// CodeMirror, copyright © by Marijn Haverbeke and others // Distributed under an MIT license: codemirror.net/LICENSE

(function(mod) {

if (typeof exports == "object" && typeof module == "object") // CommonJS
  mod(require("../../lib/codemirror"), require("../javascript/javascript"), require("../css/css"), require("../htmlmixed/htmlmixed"));
else if (typeof define == "function" && define.amd) // AMD
  define(["../../lib/codemirror", "../javascript/javascript", "../css/css", "../htmlmixed/htmlmixed"], mod);
else // Plain browser env
  mod(CodeMirror);

})(function(CodeMirror) { “use strict”;

CodeMirror.defineMode('jade', function (config) {

// token types
var KEYWORD = 'keyword';
var DOCTYPE = 'meta';
var ID = 'builtin';
var CLASS = 'qualifier';

var ATTRS_NEST = {
  '{': '}',
  '(': ')',
  '[': ']'
};

var jsMode = CodeMirror.getMode(config, 'javascript');

function State() {
  this.javaScriptLine = false;
  this.javaScriptLineExcludesColon = false;

  this.javaScriptArguments = false;
  this.javaScriptArgumentsDepth = 0;

  this.isInterpolating = false;
  this.interpolationNesting = 0;

  this.jsState = jsMode.startState();

  this.restOfLine = '';

  this.isIncludeFiltered = false;
  this.isEach = false;

  this.lastTag = '';
  this.scriptType = '';

  // Attributes Mode
  this.isAttrs = false;
  this.attrsNest = [];
  this.inAttributeName = true;
  this.attributeIsType = false;
  this.attrValue = '';

  // Indented Mode
  this.indentOf = Infinity;
  this.indentToken = '';

  this.innerMode = null;
  this.innerState = null;

  this.innerModeForLine = false;
}
/**
 * Safely copy a state
 *
 * @return {State}
 */
State.prototype.copy = function () {
  var res = new State();
  res.javaScriptLine = this.javaScriptLine;
  res.javaScriptLineExcludesColon = this.javaScriptLineExcludesColon;
  res.javaScriptArguments = this.javaScriptArguments;
  res.javaScriptArgumentsDepth = this.javaScriptArgumentsDepth;
  res.isInterpolating = this.isInterpolating;
  res.interpolationNesting = this.intpolationNesting;

  res.jsState = CodeMirror.copyState(jsMode, this.jsState);

  res.innerMode = this.innerMode;
  if (this.innerMode && this.innerState) {
    res.innerState = CodeMirror.copyState(this.innerMode, this.innerState);
  }

  res.restOfLine = this.restOfLine;

  res.isIncludeFiltered = this.isIncludeFiltered;
  res.isEach = this.isEach;
  res.lastTag = this.lastTag;
  res.scriptType = this.scriptType;
  res.isAttrs = this.isAttrs;
  res.attrsNest = this.attrsNest.slice();
  res.inAttributeName = this.inAttributeName;
  res.attributeIsType = this.attributeIsType;
  res.attrValue = this.attrValue;
  res.indentOf = this.indentOf;
  res.indentToken = this.indentToken;

  res.innerModeForLine = this.innerModeForLine;

  return res;
};

function javaScript(stream, state) {
  if (stream.sol()) {
    // if javaScriptLine was set at end of line, ignore it
    state.javaScriptLine = false;
    state.javaScriptLineExcludesColon = false;
  }
  if (state.javaScriptLine) {
    if (state.javaScriptLineExcludesColon && stream.peek() === ':') {
      state.javaScriptLine = false;
      state.javaScriptLineExcludesColon = false;
      return;
    }
    var tok = jsMode.token(stream, state.jsState);
    if (stream.eol()) state.javaScriptLine = false;
    return tok || true;
  }
}
function javaScriptArguments(stream, state) {
  if (state.javaScriptArguments) {
    if (state.javaScriptArgumentsDepth === 0 && stream.peek() !== '(') {
      state.javaScriptArguments = false;
      return;
    }
    if (stream.peek() === '(') {
      state.javaScriptArgumentsDepth++;
    } else if (stream.peek() === ')') {
      state.javaScriptArgumentsDepth--;
    }
    if (state.javaScriptArgumentsDepth === 0) {
      state.javaScriptArguments = false;
      return;
    }

    var tok = jsMode.token(stream, state.jsState);
    return tok || true;
  }
}

function yieldStatement(stream) {
  if (stream.match(/^yield\b/)) {
      return 'keyword';
  }
}

function doctype(stream) {
  if (stream.match(/^(?:doctype) *([^\n]+)?/)) {
      return DOCTYPE;
  }
}

function interpolation(stream, state) {
  if (stream.match('#{')) {
    state.isInterpolating = true;
    state.interpolationNesting = 0;
    return 'punctuation';
  }
}

function interpolationContinued(stream, state) {
  if (state.isInterpolating) {
    if (stream.peek() === '}') {
      state.interpolationNesting--;
      if (state.interpolationNesting < 0) {
        stream.next();
        state.isInterpolating = false;
        return 'puncutation';
      }
    } else if (stream.peek() === '{') {
      state.interpolationNesting++;
    }
    return jsMode.token(stream, state.jsState) || true;
  }
}

function caseStatement(stream, state) {
  if (stream.match(/^case\b/)) {
    state.javaScriptLine = true;
    return KEYWORD;
  }
}

function when(stream, state) {
  if (stream.match(/^when\b/)) {
    state.javaScriptLine = true;
    state.javaScriptLineExcludesColon = true;
    return KEYWORD;
  }
}

function defaultStatement(stream) {
  if (stream.match(/^default\b/)) {
    return KEYWORD;
  }
}

function extendsStatement(stream, state) {
  if (stream.match(/^extends?\b/)) {
    state.restOfLine = 'string';
    return KEYWORD;
  }
}

function append(stream, state) {
  if (stream.match(/^append\b/)) {
    state.restOfLine = 'variable';
    return KEYWORD;
  }
}
function prepend(stream, state) {
  if (stream.match(/^prepend\b/)) {
    state.restOfLine = 'variable';
    return KEYWORD;
  }
}
function block(stream, state) {
  if (stream.match(/^block\b *(?:(prepend|append)\b)?/)) {
    state.restOfLine = 'variable';
    return KEYWORD;
  }
}

function include(stream, state) {
  if (stream.match(/^include\b/)) {
    state.restOfLine = 'string';
    return KEYWORD;
  }
}

function includeFiltered(stream, state) {
  if (stream.match(/^include:([a-zA-Z0-9\-]+)/, false) && stream.match('include')) {
    state.isIncludeFiltered = true;
    return KEYWORD;
  }
}

function includeFilteredContinued(stream, state) {
  if (state.isIncludeFiltered) {
    var tok = filter(stream, state);
    state.isIncludeFiltered = false;
    state.restOfLine = 'string';
    return tok;
  }
}

function mixin(stream, state) {
  if (stream.match(/^mixin\b/)) {
    state.javaScriptLine = true;
    return KEYWORD;
  }
}

function call(stream, state) {
  if (stream.match(/^\+([-\w]+)/)) {
    if (!stream.match(/^\( *[-\w]+ *=/, false)) {
      state.javaScriptArguments = true;
      state.javaScriptArgumentsDepth = 0;
    }
    return 'variable';
  }
  if (stream.match(/^\+#{/, false)) {
    stream.next();
    state.mixinCallAfter = true;
    return interpolation(stream, state);
  }
}
function callArguments(stream, state) {
  if (state.mixinCallAfter) {
    state.mixinCallAfter = false;
    if (!stream.match(/^\( *[-\w]+ *=/, false)) {
      state.javaScriptArguments = true;
      state.javaScriptArgumentsDepth = 0;
    }
    return true;
  }
}

function conditional(stream, state) {
  if (stream.match(/^(if|unless|else if|else)\b/)) {
    state.javaScriptLine = true;
    return KEYWORD;
  }
}

function each(stream, state) {
  if (stream.match(/^(- *)?(each|for)\b/)) {
    state.isEach = true;
    return KEYWORD;
  }
}
function eachContinued(stream, state) {
  if (state.isEach) {
    if (stream.match(/^ in\b/)) {
      state.javaScriptLine = true;
      state.isEach = false;
      return KEYWORD;
    } else if (stream.sol() || stream.eol()) {
      state.isEach = false;
    } else if (stream.next()) {
      while (!stream.match(/^ in\b/, false) && stream.next());
      return 'variable';
    }
  }
}

function whileStatement(stream, state) {
  if (stream.match(/^while\b/)) {
    state.javaScriptLine = true;
    return KEYWORD;
  }
}

function tag(stream, state) {
  var captures;
  if (captures = stream.match(/^(\w(?:[-:\w]*\w)?)\/?/)) {
    state.lastTag = captures[1].toLowerCase();
    if (state.lastTag === 'script') {
      state.scriptType = 'application/javascript';
    }
    return 'tag';
  }
}

function filter(stream, state) {
  if (stream.match(/^:([\w\-]+)/)) {
    var innerMode;
    if (config && config.innerModes) {
      innerMode = config.innerModes(stream.current().substring(1));
    }
    if (!innerMode) {
      innerMode = stream.current().substring(1);
    }
    if (typeof innerMode === 'string') {
      innerMode = CodeMirror.getMode(config, innerMode);
    }
    setInnerMode(stream, state, innerMode);
    return 'atom';
  }
}

function code(stream, state) {
  if (stream.match(/^(!?=|-)/)) {
    state.javaScriptLine = true;
    return 'punctuation';
  }
}

function id(stream) {
  if (stream.match(/^#([\w-]+)/)) {
    return ID;
  }
}

function className(stream) {
  if (stream.match(/^\.([\w-]+)/)) {
    return CLASS;
  }
}

function attrs(stream, state) {
  if (stream.peek() == '(') {
    stream.next();
    state.isAttrs = true;
    state.attrsNest = [];
    state.inAttributeName = true;
    state.attrValue = '';
    state.attributeIsType = false;
    return 'punctuation';
  }
}

function attrsContinued(stream, state) {
  if (state.isAttrs) {
    if (ATTRS_NEST[stream.peek()]) {
      state.attrsNest.push(ATTRS_NEST[stream.peek()]);
    }
    if (state.attrsNest[state.attrsNest.length - 1] === stream.peek()) {
      state.attrsNest.pop();
    } else  if (stream.eat(')')) {
      state.isAttrs = false;
      return 'punctuation';
    }
    if (state.inAttributeName && stream.match(/^[^=,\)!]+/)) {
      if (stream.peek() === '=' || stream.peek() === '!') {
        state.inAttributeName = false;
        state.jsState = jsMode.startState();
        if (state.lastTag === 'script' && stream.current().trim().toLowerCase() === 'type') {
          state.attributeIsType = true;
        } else {
          state.attributeIsType = false;
        }
      }
      return 'attribute';
    }

    var tok = jsMode.token(stream, state.jsState);
    if (state.attributeIsType && tok === 'string') {
      state.scriptType = stream.current().toString();
    }
    if (state.attrsNest.length === 0 && (tok === 'string' || tok === 'variable' || tok === 'keyword')) {
      try {
        Function('', 'var x ' + state.attrValue.replace(/,\s*$/, '').replace(/^!/, ''));
        state.inAttributeName = true;
        state.attrValue = '';
        stream.backUp(stream.current().length);
        return attrsContinued(stream, state);
      } catch (ex) {
        //not the end of an attribute
      }
    }
    state.attrValue += stream.current();
    return tok || true;
  }
}

function attributesBlock(stream, state) {
  if (stream.match(/^&attributes\b/)) {
    state.javaScriptArguments = true;
    state.javaScriptArgumentsDepth = 0;
    return 'keyword';
  }
}

function indent(stream) {
  if (stream.sol() && stream.eatSpace()) {
    return 'indent';
  }
}

function comment(stream, state) {
  if (stream.match(/^ *\/\/(-)?([^\n]*)/)) {
    state.indentOf = stream.indentation();
    state.indentToken = 'comment';
    return 'comment';
  }
}

function colon(stream) {
  if (stream.match(/^: */)) {
    return 'colon';
  }
}

function text(stream, state) {
  if (stream.match(/^(?:\| ?| )([^\n]+)/)) {
    return 'string';
  }
  if (stream.match(/^(<[^\n]*)/, false)) {
    // html string
    setInnerMode(stream, state, 'htmlmixed');
    state.innerModeForLine = true;
    return innerMode(stream, state, true);
  }
}

function dot(stream, state) {
  if (stream.eat('.')) {
    var innerMode = null;
    if (state.lastTag === 'script' && state.scriptType.toLowerCase().indexOf('javascript') != -1) {
      innerMode = state.scriptType.toLowerCase().replace(/"|'/g, '');
    } else if (state.lastTag === 'style') {
      innerMode = 'css';
    }
    setInnerMode(stream, state, innerMode);
    return 'dot';
  }
}

function fail(stream) {
  stream.next();
  return null;
}

function setInnerMode(stream, state, mode) {
  mode = CodeMirror.mimeModes[mode] || mode;
  mode = config.innerModes ? config.innerModes(mode) || mode : mode;
  mode = CodeMirror.mimeModes[mode] || mode;
  mode = CodeMirror.getMode(config, mode);
  state.indentOf = stream.indentation();

  if (mode && mode.name !== 'null') {
    state.innerMode = mode;
  } else {
    state.indentToken = 'string';
  }
}
function innerMode(stream, state, force) {
  if (stream.indentation() > state.indentOf || (state.innerModeForLine && !stream.sol()) || force) {
    if (state.innerMode) {
      if (!state.innerState) {
        state.innerState = state.innerMode.startState ? state.innerMode.startState(stream.indentation()) : {};
      }
      return stream.hideFirstChars(state.indentOf + 2, function () {
        return state.innerMode.token(stream, state.innerState) || true;
      });
    } else {
      stream.skipToEnd();
      return state.indentToken;
    }
  } else if (stream.sol()) {
    state.indentOf = Infinity;
    state.indentToken = null;
    state.innerMode = null;
    state.innerState = null;
  }
}
function restOfLine(stream, state) {
  if (stream.sol()) {
    // if restOfLine was set at end of line, ignore it
    state.restOfLine = '';
  }
  if (state.restOfLine) {
    stream.skipToEnd();
    var tok = state.restOfLine;
    state.restOfLine = '';
    return tok;
  }
}

function startState() {
  return new State();
}
function copyState(state) {
  return state.copy();
}
/**
 * Get the next token in the stream
 *
 * @param {Stream} stream
 * @param {State} state
 */
function nextToken(stream, state) {
  var tok = innerMode(stream, state)
    || restOfLine(stream, state)
    || interpolationContinued(stream, state)
    || includeFilteredContinued(stream, state)
    || eachContinued(stream, state)
    || attrsContinued(stream, state)
    || javaScript(stream, state)
    || javaScriptArguments(stream, state)
    || callArguments(stream, state)

    || yieldStatement(stream, state)
    || doctype(stream, state)
    || interpolation(stream, state)
    || caseStatement(stream, state)
    || when(stream, state)
    || defaultStatement(stream, state)
    || extendsStatement(stream, state)
    || append(stream, state)
    || prepend(stream, state)
    || block(stream, state)
    || include(stream, state)
    || includeFiltered(stream, state)
    || mixin(stream, state)
    || call(stream, state)
    || conditional(stream, state)
    || each(stream, state)
    || whileStatement(stream, state)
    || tag(stream, state)
    || filter(stream, state)
    || code(stream, state)
    || id(stream, state)
    || className(stream, state)
    || attrs(stream, state)
    || attributesBlock(stream, state)
    || indent(stream, state)
    || text(stream, state)
    || comment(stream, state)
    || colon(stream, state)
    || dot(stream, state)
    || fail(stream, state);

  return tok === true ? null : tok;
}
return {
  startState: startState,
  copyState: copyState,
  token: nextToken
};

});

CodeMirror.defineMIME('text/x-jade', 'jade');

});