// CodeMirror, copyright (c) by Marijn Haverbeke and others

// Distributed under an MIT license: http://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('tiki', function(config) {

  function inBlock(style, terminator, returnTokenizer) {

    return function(stream, state) {

      while (!stream.eol()) {

        if (stream.match(terminator)) {

          state.tokenize = inText;

          break;

        }

        stream.next();

      }



      if (returnTokenizer) state.tokenize = returnTokenizer;



      return style;

    };

  }



  function inLine(style) {

    return function(stream, state) {

      while(!stream.eol()) {

        stream.next();

      }

      state.tokenize = inText;

      return style;

    };

  }



  function inText(stream, state) {

    function chain(parser) {

      state.tokenize = parser;

      return parser(stream, state);

    }



    var sol = stream.sol();

    var ch = stream.next();



    //non start of line

    switch (ch) { //switch is generally much faster than if, so it is used here

    case "{": //plugin

      stream.eat("/");

      stream.eatSpace();

      stream.eatWhile(/[^\s\u00a0=\"\'\/?(}]/);

      state.tokenize = inPlugin;

      return "tag";

    case "_": //bold

      if (stream.eat("_"))

        return chain(inBlock("strong", "__", inText));

      break;

    case "'": //italics

      if (stream.eat("'"))

        return chain(inBlock("em", "''", inText));

      break;

    case "(":// Wiki Link

      if (stream.eat("("))

        return chain(inBlock("variable-2", "))", inText));

      break;

    case "[":// Weblink

      return chain(inBlock("variable-3", "]", inText));

      break;

    case "|": //table

      if (stream.eat("|"))

        return chain(inBlock("comment", "||"));

      break;

    case "-":

      if (stream.eat("=")) {//titleBar

        return chain(inBlock("header string", "=-", inText));

      } else if (stream.eat("-")) {//deleted

        return chain(inBlock("error tw-deleted", "--", inText));

      }

      break;

    case "=": //underline

      if (stream.match("=="))

        return chain(inBlock("tw-underline", "===", inText));

      break;

    case ":":

      if (stream.eat(":"))

        return chain(inBlock("comment", "::"));

      break;

    case "^": //box

      return chain(inBlock("tw-box", "^"));

      break;

    case "~": //np

      if (stream.match("np~"))

        return chain(inBlock("meta", "~/np~"));

      break;

    }



    //start of line types

    if (sol) {

      switch (ch) {

      case "!": //header at start of line

        if (stream.match('!!!!!')) {

          return chain(inLine("header string"));

        } else if (stream.match('!!!!')) {

          return chain(inLine("header string"));

        } else if (stream.match('!!!')) {

          return chain(inLine("header string"));

        } else if (stream.match('!!')) {

          return chain(inLine("header string"));

        } else {

          return chain(inLine("header string"));

        }

        break;

      case "*": //unordered list line item, or <li /> at start of line

      case "#": //ordered list line item, or <li /> at start of line

      case "+": //ordered list line item, or <li /> at start of line

        return chain(inLine("tw-listitem bracket"));

        break;

      }

    }



    //stream.eatWhile(/[&{]/); was eating up plugins, turned off to act less like html and more like tiki

    return null;

  }



  var indentUnit = config.indentUnit;



  // Return variables for tokenizers

  var pluginName, type;

  function inPlugin(stream, state) {

    var ch = stream.next();

    var peek = stream.peek();



    if (ch == "}") {

      state.tokenize = inText;

      //type = ch == ")" ? "endPlugin" : "selfclosePlugin"; inPlugin

      return "tag";

    } else if (ch == "(" || ch == ")") {

      return "bracket";

    } else if (ch == "=") {

      type = "equals";



      if (peek == ">") {

        ch = stream.next();

        peek = stream.peek();

      }



      //here we detect values directly after equal character with no quotes

      if (!/[\'\"]/.test(peek)) {

        state.tokenize = inAttributeNoQuote();

      }

      //end detect values



      return "operator";

    } else if (/[\'\"]/.test(ch)) {

      state.tokenize = inAttribute(ch);

      return state.tokenize(stream, state);

    } else {

      stream.eatWhile(/[^\s\u00a0=\"\'\/?]/);

      return "keyword";

    }

  }



  function inAttribute(quote) {

    return function(stream, state) {

      while (!stream.eol()) {

        if (stream.next() == quote) {

          state.tokenize = inPlugin;

          break;

        }

      }

      return "string";

    };

  }



  function inAttributeNoQuote() {

    return function(stream, state) {

      while (!stream.eol()) {

        var ch = stream.next();

        var peek = stream.peek();

        if (ch == " " || ch == "," || /[ )}]/.test(peek)) {

      state.tokenize = inPlugin;

      break;

    }

  }

  return "string";

};

                     }



var curState, setStyle;

function pass() {

  for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);

}



function cont() {

  pass.apply(null, arguments);

  return true;

}



function pushContext(pluginName, startOfLine) {

  var noIndent = curState.context && curState.context.noIndent;

  curState.context = {

    prev: curState.context,

    pluginName: pluginName,

    indent: curState.indented,

    startOfLine: startOfLine,

    noIndent: noIndent

  };

}



function popContext() {

  if (curState.context) curState.context = curState.context.prev;

}



function element(type) {

  if (type == "openPlugin") {curState.pluginName = pluginName; return cont(attributes, endplugin(curState.startOfLine));}

  else if (type == "closePlugin") {

    var err = false;

    if (curState.context) {

      err = curState.context.pluginName != pluginName;

      popContext();

    } else {

      err = true;

    }

    if (err) setStyle = "error";

    return cont(endcloseplugin(err));

  }

  else if (type == "string") {

    if (!curState.context || curState.context.name != "!cdata") pushContext("!cdata");

    if (curState.tokenize == inText) popContext();

    return cont();

  }

  else return cont();

}



function endplugin(startOfLine) {

  return function(type) {

    if (

      type == "selfclosePlugin" ||

        type == "endPlugin"

    )

      return cont();

    if (type == "endPlugin") {pushContext(curState.pluginName, startOfLine); return cont();}

    return cont();

  };

}



function endcloseplugin(err) {

  return function(type) {

    if (err) setStyle = "error";

    if (type == "endPlugin") return cont();

    return pass();

  };

}



function attributes(type) {

  if (type == "keyword") {setStyle = "attribute"; return cont(attributes);}

  if (type == "equals") return cont(attvalue, attributes);

  return pass();

}

function attvalue(type) {

  if (type == "keyword") {setStyle = "string"; return cont();}

  if (type == "string") return cont(attvaluemaybe);

  return pass();

}

function attvaluemaybe(type) {

  if (type == "string") return cont(attvaluemaybe);

  else return pass();

}

return {

  startState: function() {

    return {tokenize: inText, cc: [], indented: 0, startOfLine: true, pluginName: null, context: null};

  },

  token: function(stream, state) {

    if (stream.sol()) {

      state.startOfLine = true;

      state.indented = stream.indentation();

    }

    if (stream.eatSpace()) return null;



    setStyle = type = pluginName = null;

    var style = state.tokenize(stream, state);

    if ((style || type) && style != "comment") {

      curState = state;

      while (true) {

        var comb = state.cc.pop() || element;

        if (comb(type || style)) break;

      }

    }

    state.startOfLine = false;

    return setStyle || style;

  },

  indent: function(state, textAfter) {

    var context = state.context;

    if (context && context.noIndent) return 0;

    if (context && /^{\//.test(textAfter))

        context = context.prev;

        while (context && !context.startOfLine)

          context = context.prev;

        if (context) return context.indent + indentUnit;

        else return 0;

       },

    electricChars: "/"

  };

});



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



});

