// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://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("apl", function () {
    var builtInOps = {
      ".": "innerProduct",
      "\\": "scan",
      "/": "reduce",
      "⌿": "reduce1Axis",
      "⍀": "scan1Axis",
      "¨": "each",
      "⍣": "power"
    };
    var builtInFuncs = {
      "+": ["conjugate", "add"],
      "−": ["negate", "subtract"],
      "×": ["signOf", "multiply"],
      "÷": ["reciprocal", "divide"],
      "⌈": ["ceiling", "greaterOf"],
      "⌊": ["floor", "lesserOf"],
      "∣": ["absolute", "residue"],
      "⍳": ["indexGenerate", "indexOf"],
      "?": ["roll", "deal"],
      "⋆": ["exponentiate", "toThePowerOf"],
      "⍟": ["naturalLog", "logToTheBase"],
      "○": ["piTimes", "circularFuncs"],
      "!": ["factorial", "binomial"],
      "⌹": ["matrixInverse", "matrixDivide"],
      "<": [null, "lessThan"],
      "≤": [null, "lessThanOrEqual"],
      "=": [null, "equals"],
      ">": [null, "greaterThan"],
      "≥": [null, "greaterThanOrEqual"],
      "≠": [null, "notEqual"],
      "≡": ["depth", "match"],
      "≢": [null, "notMatch"],
      "∈": ["enlist", "membership"],
      "⍷": [null, "find"],
      "∪": ["unique", "union"],
      "∩": [null, "intersection"],
      "∼": ["not", "without"],
      "∨": [null, "or"],
      "∧": [null, "and"],
      "⍱": [null, "nor"],
      "⍲": [null, "nand"],
      "⍴": ["shapeOf", "reshape"],
      ",": ["ravel", "catenate"],
      "⍪": [null, "firstAxisCatenate"],
      "⌽": ["reverse", "rotate"],
      "⊖": ["axis1Reverse", "axis1Rotate"],
      "⍉": ["transpose", null],
      "↑": ["first", "take"],
      "↓": [null, "drop"],
      "⊂": ["enclose", "partitionWithAxis"],
      "⊃": ["diclose", "pick"],
      "⌷": [null, "index"],
      "⍋": ["gradeUp", null],
      "⍒": ["gradeDown", null],
      "⊤": ["encode", null],
      "⊥": ["decode", null],
      "⍕": ["format", "formatByExample"],
      "⍎": ["execute", null],
      "⊣": ["stop", "left"],
      "⊢": ["pass", "right"]
    };

    var isOperator = /[\.\/⌿⍀¨⍣]/;
    var isNiladic = /⍬/;
    var isFunction = /[\+−×÷⌈⌊∣⍳\?⋆⍟○!⌹<≤=>≥≠≡≢∈⍷∪∩∼∨∧⍱⍲⍴,⍪⌽⊖⍉↑↓⊂⊃⌷⍋⍒⊤⊥⍕⍎⊣⊢]/;
    var isArrow = /←/;
    var isComment = /[⍝#].*$/;

    var stringEater = function (type) {
      var prev;
      prev = false;
      return function (c) {
        prev = c;
        if (c === type) {
          return prev === "\\";
        }
        return true;
      };
    };
    return {
      startState: function () {
        return {
          prev: false,
          func: false,
          op: false,
          string: false,
          escape: false
        };
      },
      token: function (stream, state) {
        var ch, funcName;
        if (stream.eatSpace()) {
          return null;
        }
        ch = stream.next();
        if (ch === '"' || ch === "'") {
          stream.eatWhile(stringEater(ch));
          stream.next();
          state.prev = true;
          return "string";
        }
        if (/[\[{\(]/.test(ch)) {
          state.prev = false;
          return null;
        }
        if (/[\]}\)]/.test(ch)) {
          state.prev = true;
          return null;
        }
        if (isNiladic.test(ch)) {
          state.prev = false;
          return "niladic";
        }
        if (/[¯\d]/.test(ch)) {
          if (state.func) {
            state.func = false;
            state.prev = false;
          } else {
            state.prev = true;
          }
          stream.eatWhile(/[\w\.]/);
          return "number";
        }
        if (isOperator.test(ch)) {
          return "operator apl-" + builtInOps[ch];
        }
        if (isArrow.test(ch)) {
          return "apl-arrow";
        }
        if (isFunction.test(ch)) {
          funcName = "apl-";
          if (builtInFuncs[ch] != null) {
            if (state.prev) {
              funcName += builtInFuncs[ch][1];
            } else {
              funcName += builtInFuncs[ch][0];
            }
          }
          state.func = true;
          state.prev = false;
          return "function " + funcName;
        }
        if (isComment.test(ch)) {
          stream.skipToEnd();
          return "comment";
        }
        if (ch === "∘" && stream.peek() === ".") {
          stream.next();
          return "function jot-dot";
        }
        stream.eatWhile(/[\w\$_]/);
        state.prev = true;
        return "keyword";
      }
    };
  });

  CodeMirror.defineMIME("text/apl", "apl");

});
