CodeMirror.defineMode('tiki', function(config, parserConfig) {
	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, terminator) {
		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
				type = stream.eat("/") ? "closeTag" : "openTag";
				stream.eatSpace();
				tagName = "";
				var c;
				while ((c = stream.eat(/[^\s\u00a0=\"\'\/?(}]/))) tagName += c;
				state.tokenize = inPlugin;
				return "tag";
				break;
			case "_": //bold
				if (stream.eat("_")) {
					return chain(inBlock("strong", "__", inText));
				}
				break;
			case "'": //italics
				if (stream.eat("'")) {
					// Italic text
					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;
		},
		compareStates: function(a, b) {
			if (a.indented != b.indented || a.pluginName != b.pluginName) return false;
			for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {
				if (!ca || !cb) return ca == cb;
				if (ca.pluginName != cb.pluginName) return false;
			}
		},
		electricChars: "/"
	};
});

//I figure, why not
CodeMirror.defineMIME("text/tiki", "tiki");
