/* Simple parser for CSS */

var CSSParser = Editor.Parser = (function () {
	var tokenizeCSS = (function () {
		function normal(source, setState) {
			var ch = source.next();
			if (ch == "@") {
				source.nextWhileMatches(/\w/);
				return "css-at";
			}
			else if (ch == "/" && source.equals("*")) {
				setState(inCComment);
				return null;
			}
			else if (ch == "<" && source.equals("!")) {
				setState(inSGMLComment);
				return null;
			}
			else if (ch == "=") {
				return "css-compare";
			}
			else if (source.equals("=") && (ch == "~" || ch == "|")) {
				source.next();
				return "css-compare";
			}
			else if (ch == "\"" || ch == "'") {
				setState(inString(ch));
				return null;
			}
			else if (ch == "#") {
				source.nextWhileMatches(/\w/);
				return "css-hash";
			}
			else if (ch == "!") {
				source.nextWhileMatches(/[ \t]/);
				source.nextWhileMatches(/\w/);
				return "css-important";
			}
			else if (/\d/.test(ch)) {
				source.nextWhileMatches(/[\w.%]/);
				return "css-unit";
			}
			else if (/[,.+>*\/]/.test(ch)) {
				return "css-select-op";
			}
			else if (/[;{}:\[\]]/.test(ch)) {
				return "css-punctuation";
			}
			else {
				source.nextWhileMatches(/[\w\\\-_]/);
				return "css-identifier";
			}
		}

		function inCComment(source, setState) {
			var maybeEnd = false;
			while (!source.endOfLine()) {
				var ch = source.next();
				if (maybeEnd && ch == "/") {
					setState(normal);
					break;
				}
				maybeEnd = (ch == "*");
			}
			return "css-comment";
		}

		function inSGMLComment(source, setState) {
			var dashes = 0;
			while (!source.endOfLine()) {
				var ch = source.next();
				if (dashes >= 2 && ch == ">") {
					setState(normal);
					break;
				}
				dashes = (ch == "-") ? dashes + 1 : 0;
			}
			return "css-comment";
		}

		function inString(quote) {
			return function (source, setState) {
				var escaped = false;
				while (!source.endOfLine()) {
					var ch = source.next();
					if (ch == quote && !escaped)
						break;
					escaped = !escaped && ch == "\\";
				}
				if (!escaped)
					setState(normal);
				return "css-string";
			};
		}

		return function (source, startState) {
			return tokenizer(source, startState || normal);
		};
	})();

	function indentCSS(inBraces, inRule, base) {
		return function (nextChars) {
			if (!inBraces || /^\}/.test(nextChars)) return base;
			else if (inRule) return base + indentUnit * 2;
			else return base + indentUnit;
		};
	}

	// This is a very simplistic parser -- since CSS does not really
	// nest, it works acceptably well, but some nicer colouroing could
	// be provided with a more complicated parser.
	function parseCSS(source, basecolumn) {
		basecolumn = basecolumn || 0;
		var tokens = tokenizeCSS(source);
		var inBraces = false, inRule = false, inDecl = false;
		var iter = {
			next: function () {
				var token = tokens.next(), style = token.style, content = token.content;

				if (style == "css-hash")
					style = token.style = inRule ? "css-colorcode" : "css-identifier";
				if (style == "css-identifier") {
					if (inRule) token.style = "css-value";
					else if (!inBraces && !inDecl) token.style = "css-selector";
				}

				if (content == "\n")
					token.indentation = indentCSS(inBraces, inRule, basecolumn);

				if (content == "{" && inDecl == "@media")
					inDecl = false;
				else if (content == "{")
					inBraces = true;
				else if (content == "}")
					inBraces = inRule = inDecl = false;
				else if (content == ";")
					inRule = inDecl = false;
				else if (inBraces && style != "css-comment" && style != "whitespace")
					inRule = true;
				else if (!inBraces && style == "css-at")
					inDecl = content;

				return token;
			},

			copy: function () {
				var _inBraces = inBraces, _inRule = inRule, _tokenState = tokens.state;
				return function (source) {
					tokens = tokenizeCSS(source, _tokenState);
					inBraces = _inBraces;
					inRule = _inRule;
					return iter;
				};
			}
		};
		return iter;
	}

	return {make: parseCSS, electricChars: "}"};
})();
