// 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"));
else if (typeof define == "function" && define.amd) // AMD
  define(["../../lib/codemirror"], mod);
else // Plain browser env
  mod(CodeMirror);

})(function(CodeMirror) {

"use strict";

CodeMirror.defineMode("elm", function() {

  function switchState(source, setState, f) {
    setState(f);
    return f(source, setState);
  }

  // These should all be Unicode extended, as per the Haskell 2010 report
  var smallRE = /[a-z_]/;
  var largeRE = /[A-Z]/;
  var digitRE = /[0-9]/;
  var hexitRE = /[0-9A-Fa-f]/;
  var octitRE = /[0-7]/;
  var idRE = /[a-z_A-Z0-9\']/;
  var symbolRE = /[-!#$%&*+.\/<=>?@\\^|~:\u03BB\u2192]/;
  var specialRE = /[(),;[\]`{}]/;
  var whiteCharRE = /[ \t\v\f]/; // newlines are handled in tokenizer

  function normal() {
    return function (source, setState) {
      if (source.eatWhile(whiteCharRE)) {
        return null;
      }

      var ch = source.next();
      if (specialRE.test(ch)) {
        if (ch == '{' && source.eat('-')) {
          var t = "comment";
          if (source.eat('#')) t = "meta";
          return switchState(source, setState, ncomment(t, 1));
        }
        return null;
      }

      if (ch == '\'') {
        if (source.eat('\\'))
          source.next();  // should handle other escapes here
        else
          source.next();

        if (source.eat('\''))
          return "string";
        return "error";
      }

      if (ch == '"') {
        return switchState(source, setState, stringLiteral);
      }

      if (largeRE.test(ch)) {
        source.eatWhile(idRE);
        if (source.eat('.'))
          return "qualifier";
        return "variable-2";
      }

      if (smallRE.test(ch)) {
        var isDef = source.pos === 1;
        source.eatWhile(idRE);
        return isDef ? "type" : "variable";
      }

      if (digitRE.test(ch)) {
        if (ch == '0') {
          if (source.eat(/[xX]/)) {
            source.eatWhile(hexitRE); // should require at least 1
            return "integer";
          }
          if (source.eat(/[oO]/)) {
            source.eatWhile(octitRE); // should require at least 1
            return "number";
          }
        }
        source.eatWhile(digitRE);
        var t = "number";
        if (source.eat('.')) {
          t = "number";
          source.eatWhile(digitRE); // should require at least 1
        }
        if (source.eat(/[eE]/)) {
          t = "number";
          source.eat(/[-+]/);
          source.eatWhile(digitRE); // should require at least 1
        }
        return t;
      }

      if (symbolRE.test(ch)) {
        if (ch == '-' && source.eat(/-/)) {
          source.eatWhile(/-/);
          if (!source.eat(symbolRE)) {
            source.skipToEnd();
            return "comment";
          }
        }
        source.eatWhile(symbolRE);
        return "builtin";
      }

      return "error";
    }
  }

  function ncomment(type, nest) {
    if (nest == 0) {
      return normal();
    }
    return function(source, setState) {
      var currNest = nest;
      while (!source.eol()) {
        var ch = source.next();
        if (ch == '{' && source.eat('-')) {
          ++currNest;
        } else if (ch == '-' && source.eat('}')) {
          --currNest;
          if (currNest == 0) {
            setState(normal());
            return type;
          }
        }
      }
      setState(ncomment(type, currNest));
      return type;
    }
  }

  function stringLiteral(source, setState) {
    while (!source.eol()) {
      var ch = source.next();
      if (ch == '"') {
        setState(normal());
        return "string";
      }
      if (ch == '\\') {
        if (source.eol() || source.eat(whiteCharRE)) {
          setState(stringGap);
          return "string";
        }
        if (!source.eat('&')) source.next(); // should handle other escapes here
      }
    }
    setState(normal());
    return "error";
  }

  function stringGap(source, setState) {
    if (source.eat('\\')) {
      return switchState(source, setState, stringLiteral);
    }
    source.next();
    setState(normal());
    return "error";
  }

  var wellKnownWords = (function() {
    var wkw = {};

    var keywords = [
      "case", "of", "as",
      "if", "then", "else",
      "let", "in",
      "infix", "infixl", "infixr",
      "type", "alias",
      "input", "output", "foreign", "loopback",
      "module", "where", "import", "exposing",
      "_", "..", "|", ":", "=", "\\", "\"", "->", "<-"
    ];

    for (var i = keywords.length; i--;)
      wkw[keywords[i]] = "keyword";

    return wkw;
  })();

  return {
    startState: function ()  { return { f: normal() }; },
    copyState:  function (s) { return { f: s.f }; },

    token: function(stream, state) {
      var t = state.f(stream, function(s) { state.f = s; });
      var w = stream.current();
      return (wellKnownWords.hasOwnProperty(w)) ? wellKnownWords[w] : t;
    }
  };

});

CodeMirror.defineMIME("text/x-elm", "elm");

});