ace.define("ace/ext/beautify", ["require", "exports", "module", "ace/token_iterator"], function (require, exports, module) {
  "use strict";

  var TokenIterator = require("../token_iterator").TokenIterator;

  function is(token, type) {
    return token.type.lastIndexOf(type + ".xml") > -1;
  }

  exports.singletonTags = ["area", "base", "br", "col", "command", "embed", "hr", "html", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"];
  exports.blockTags = ["article", "aside", "blockquote", "body", "div", "dl", "fieldset", "footer", "form", "head", "header", "html", "nav", "ol", "p", "script", "section", "style", "table", "tbody", "tfoot", "thead", "ul"];

  exports.beautify = function (session) {
    var iterator = new TokenIterator(session, 0, 0);
    var token = iterator.getCurrentToken();
    var tabString = session.getTabString();
    var singletonTags = exports.singletonTags;
    var blockTags = exports.blockTags;
    var nextToken;
    var breakBefore = false;
    var spaceBefore = false;
    var spaceAfter = false;
    var code = "";
    var value = "";
    var tagName = "";
    var depth = 0;
    var lastDepth = 0;
    var lastIndent = 0;
    var indent = 0;
    var unindent = 0;
    var roundDepth = 0;
    var curlyDepth = 0;
    var row;
    var curRow = 0;
    var rowsToAdd = 0;
    var rowTokens = [];
    var abort = false;
    var i;
    var indentNextLine = false;
    var inTag = false;
    var inCSS = false;
    var inBlock = false;
    var levels = {
      0: 0
    };
    var parents = [];

    var trimNext = function trimNext() {
      if (nextToken && nextToken.value && nextToken.type !== 'string.regexp') nextToken.value = nextToken.value.replace(/^\s*/, "");
    };

    var trimLine = function trimLine() {
      code = code.replace(/ +$/, "");
    };

    var trimCode = function trimCode() {
      code = code.trimRight();
      breakBefore = false;
    };

    while (token !== null) {
      curRow = iterator.getCurrentTokenRow();
      rowTokens = iterator.$rowTokens;
      nextToken = iterator.stepForward();

      if (typeof token !== "undefined") {
        value = token.value;
        unindent = 0;
        inCSS = tagName === "style" || session.$modeId === "ace/mode/css";

        if (is(token, "tag-open")) {
          inTag = true;
          if (nextToken) inBlock = blockTags.indexOf(nextToken.value) !== -1;

          if (value === "</") {
            if (inBlock && !breakBefore && rowsToAdd < 1) rowsToAdd++;
            if (inCSS) rowsToAdd = 1;
            unindent = 1;
            inBlock = false;
          }
        } else if (is(token, "tag-close")) {
          inTag = false;
        } else if (is(token, "comment.start")) {
          inBlock = true;
        } else if (is(token, "comment.end")) {
          inBlock = false;
        }

        if (!inTag && !rowsToAdd && token.type === "paren.rparen" && token.value.substr(0, 1) === "}") {
          rowsToAdd++;
        }

        if (curRow !== row) {
          rowsToAdd = curRow;
          if (row) rowsToAdd -= row;
        }

        if (rowsToAdd) {
          trimCode();

          for (; rowsToAdd > 0; rowsToAdd--) {
            code += "\n";
          }

          breakBefore = true;
          if (!is(token, "comment") && !token.type.match(/^(comment|string)$/)) value = value.trimLeft();
        }

        if (value) {
          if (token.type === "keyword" && value.match(/^(if|else|elseif|for|foreach|while|switch)$/)) {
            parents[depth] = value;
            trimNext();
            spaceAfter = true;

            if (value.match(/^(else|elseif)$/)) {
              if (code.match(/\}[\s]*$/)) {
                trimCode();
                spaceBefore = true;
              }
            }
          } else if (token.type === "paren.lparen") {
            trimNext();

            if (value.substr(-1) === "{") {
              spaceAfter = true;
              indentNextLine = false;
              if (!inTag) rowsToAdd = 1;
            }

            if (value.substr(0, 1) === "{") {
              spaceBefore = true;

              if (code.substr(-1) !== '[' && code.trimRight().substr(-1) === '[') {
                trimCode();
                spaceBefore = false;
              } else if (code.trimRight().substr(-1) === ')') {
                trimCode();
              } else {
                trimLine();
              }
            }
          } else if (token.type === "paren.rparen") {
            unindent = 1;

            if (value.substr(0, 1) === "}") {
              if (parents[depth - 1] === 'case') unindent++;

              if (code.trimRight().substr(-1) === '{') {
                trimCode();
              } else {
                spaceBefore = true;
                if (inCSS) rowsToAdd += 2;
              }
            }

            if (value.substr(0, 1) === "]") {
              if (code.substr(-1) !== '}' && code.trimRight().substr(-1) === '}') {
                spaceBefore = false;
                indent++;
                trimCode();
              }
            }

            if (value.substr(0, 1) === ")") {
              if (code.substr(-1) !== '(' && code.trimRight().substr(-1) === '(') {
                spaceBefore = false;
                indent++;
                trimCode();
              }
            }

            trimLine();
          } else if ((token.type === "keyword.operator" || token.type === "keyword") && value.match(/^(=|==|===|!=|!==|&&|\|\||and|or|xor|\+=|.=|>|>=|<|<=|=>)$/)) {
            trimCode();
            trimNext();
            spaceBefore = true;
            spaceAfter = true;
          } else if (token.type === "punctuation.operator" && value === ';') {
            trimCode();
            trimNext();
            spaceAfter = true;
            if (inCSS) rowsToAdd++;
          } else if (token.type === "punctuation.operator" && value.match(/^(:|,)$/)) {
            trimCode();
            trimNext();

            if (value.match(/^(,)$/) && curlyDepth > 0 && roundDepth === 0) {
              rowsToAdd++;
            } else {
              spaceAfter = true;
              breakBefore = false;
            }
          } else if (token.type === "support.php_tag" && value === "?>" && !breakBefore) {
            trimCode();
            spaceBefore = true;
          } else if (is(token, "attribute-name") && code.substr(-1).match(/^\s$/)) {
            spaceBefore = true;
          } else if (is(token, "attribute-equals")) {
            trimLine();
            trimNext();
          } else if (is(token, "tag-close")) {
            trimLine();
            if (value === "/>") spaceBefore = true;
          }

          if (breakBefore && !(token.type.match(/^(comment)$/) && !value.substr(0, 1).match(/^[/#]$/)) && !(token.type.match(/^(string)$/) && !value.substr(0, 1).match(/^['"]$/))) {
            indent = lastIndent;

            if (depth > lastDepth) {
              indent++;

              for (i = depth; i > lastDepth; i--) {
                levels[i] = indent;
              }
            } else if (depth < lastDepth) indent = levels[depth];

            lastDepth = depth;
            lastIndent = indent;
            if (unindent) indent -= unindent;

            if (indentNextLine && !roundDepth) {
              indent++;
              indentNextLine = false;
            }

            for (i = 0; i < indent; i++) {
              code += tabString;
            }
          }

          if (token.type === "keyword" && value.match(/^(case|default)$/)) {
            parents[depth] = value;
            depth++;
          }

          if (token.type === "keyword" && value.match(/^(break)$/)) {
            if (parents[depth - 1] && parents[depth - 1].match(/^(case|default)$/)) {
              depth--;
            }
          }

          if (token.type === "paren.lparen") {
            roundDepth += (value.match(/\(/g) || []).length;
            curlyDepth += (value.match(/\{/g) || []).length;
            depth += value.length;
          }

          if (token.type === "keyword" && value.match(/^(if|else|elseif|for|while)$/)) {
            indentNextLine = true;
            roundDepth = 0;
          } else if (!roundDepth && value.trim() && token.type !== "comment") indentNextLine = false;

          if (token.type === "paren.rparen") {
            roundDepth -= (value.match(/\)/g) || []).length;
            curlyDepth -= (value.match(/\}/g) || []).length;

            for (i = 0; i < value.length; i++) {
              depth--;

              if (value.substr(i, 1) === '}' && parents[depth] === 'case') {
                depth--;
              }
            }
          }

          if (token.type == "text") value = value.replace(/\s+$/, " ");

          if (spaceBefore && !breakBefore) {
            trimLine();
            if (code.substr(-1) !== "\n") code += " ";
          }

          code += value;
          if (spaceAfter) code += " ";
          breakBefore = false;
          spaceBefore = false;
          spaceAfter = false;

          if (is(token, "tag-close") && (inBlock || blockTags.indexOf(tagName) !== -1) || is(token, "doctype") && value === ">") {
            if (inBlock && nextToken && nextToken.value === "</") rowsToAdd = -1;else rowsToAdd = 1;
          }

          if (is(token, "tag-open") && value === "</") {
            depth--;
          } else if (is(token, "tag-open") && value === "<" && singletonTags.indexOf(nextToken.value) === -1) {
            depth++;
          } else if (is(token, "tag-name")) {
            tagName = value;
          } else if (is(token, "tag-close") && value === "/>" && singletonTags.indexOf(tagName) === -1) {
            depth--;
          }

          row = curRow;
        }
      }

      token = nextToken;
    }

    code = code.trim();
    session.doc.setValue(code);
  };

  exports.commands = [{
    name: "beautify",
    description: "Format selection (Beautify)",
    exec: function exec(editor) {
      exports.beautify(editor.session);
    },
    bindKey: "Ctrl-Shift-B"
  }];
});

(function () {
  ace.require(["ace/ext/beautify"], function (m) {
    if (typeof module == "object" && typeof exports == "object" && module) {
      module.exports = m;
    }
  });
})();