/* ---------------------------------------------------------------------------
 *  Pseudo based JavaScript syntax highlighter
 *  (c) 2010 Alex Lein
 *
 *  Pseudo is freely distributable under the terms of an MIT-style license.
 *  For details, see the Prototype web site: http://www.prototypejs.org/
 *
 *--------------------------------------------------------------------------*/
var Highlight = (function() {
	var JS_COMMENTS = /\/\*[\s\S]*?\*\/|\/\/.*$\n/gm;
	var JS_STRINGS = /"([^\\"]|\\.)*"|'([^\\']|\\.)*'/gm;
	var JS_LITERALS = /\b[\d\.]+\b|\[\s*\]|\{\s*\}|\/([^\\\/]|\\.)*\/[gimy]{0,4}/g;
	//	var JS_NUMBERS = /\b\d+\b/g;
	var JS_KEYWORDS = /\b(continue|delete|do|else|else\s+if|false|for|function|if|instanceof|new|null|return|true|throw|typeof|undefined|var|void|while)\b/g;
	var JS_OBJECTS = /\b(arguments|console|document|event|frames|location|navigator|opener|parent|self|this|top|window)\b/g;
	var JS_CLASSES = new RegExp([
		"\\b(",
		"Array|Boolean|Date|Document|Function|Object|Number|String|RegExp|Window",
		"|Pseudo|Class|Ajax|Xml|Draw|DOM|Element|HTMLElement|SVGElement|VMLElement",
		")\\b"
	].join(""), "g");
	var JS_ALL = new RegExp([
		JS_COMMENTS.source,
		JS_STRINGS.source,
		JS_LITERALS.source,
		JS_KEYWORDS.source,
		JS_OBJECTS.source,
		JS_CLASSES.source
	].join("|"), "gm");

	function javascript(code, tabWidth) {
		var html = [], output;

		code.separate(JS_ALL).each(function(segment) {
			if (!segment) {
				// do not add blank matches
			} else if (segment.search(JS_COMMENTS.reset()) === 0) {
				html.push("<span class=\"comment\">" + segment + "</span>");
			} else if (segment.search(JS_STRINGS.reset()) === 0) {
				html.push("<span class=\"string\">" + segment + "</span>");
			} else if (segment.search(JS_LITERALS.reset()) === 0) {
				html.push("<span class=\"literal\">" + segment + "</span>");
			} else if (segment.search(JS_KEYWORDS.reset()) === 0) {
				html.push("<span class=\"keyword\">" + segment + "</span>");
			} else if (segment.search(JS_OBJECTS.reset()) === 0) {
				html.push("<span class=\"object\">" + segment + "</span>");
			} else if (segment.search(JS_CLASSES.reset()) === 0) {
				html.push("<span class=\"class\">" + segment + "</span>");
			} else {
				html.push(segment);
			};
		});

		output = html.join("");
		return (tabWidth) ? output.replaceAll("\t", "  ") : output;
	};


	var CSS_MEDIAS = /@media[^\{]+/gi;
	var CSS_SELECTORS = /[a-z\#\.\[][\s\S]*?\{[\s\S]*?\}/gm;
	var CSS_COMMENTS = /\/\*[\s\S]*?\*\//gm;
	var CSS_ATTRIBUTES = /[a-z\-]+[^:]/gi;
	var CSS_VALUES = /[^:]+?;/gi;
	var CSS_COLOUR_FILTER = /(#(?:[0-9a-f]{3}|[0-9a-f]{6})|(?:rgba?|hsla?)\([^)]+\))/gi;
	var CSS_COLOUR_SHOW = "<span class=\"colour\" style=\"border-color:$1;\">$1</span>";
	var CSS_SECTIONS = new RegExp([
		CSS_COMMENTS.source,
		CSS_MEDIAS.source,
		CSS_SELECTORS.source
	].join("|"), "gm");
	var CSS_SUBS = new RegExp([
		CSS_COMMENTS.source,
		CSS_ATTRIBUTES.source,
		CSS_VALUES.source
	].join("|"), "gm");

	function css(code, tabWidth) {
		var html = [], output;
		code.separate(CSS_SECTIONS).each(function(segment) {
			if (!segment) {
				// do not add blank matches
			} else if (segment.search(CSS_COMMENTS.reset()) === 0) {
				html.push("<span class=\"comment\">" + segment + "</span>");
			} else if (segment.search(CSS_MEDIAS.reset()) === 0) {
				html.push("<span class=\"media\">" + segment + "</span>");
			} else if (segment.search(CSS_SELECTORS.reset()) === 0) {
				var selector = segment.left(segment.indexOf("{"));
				html.push("<span class=\"selector\">" + selector + "</span>");
				segment.left(-selector.length).separate(CSS_SUBS).each(function(segment) {
					if (!segment) {
						// do not add blank matches
					} else if (segment.search(CSS_COMMENTS.reset()) === 0) {
						html.push("<span class=\"comment\">" + segment + "</span>");
					} else if (segment.search(CSS_ATTRIBUTES.reset()) === 0) {
						html.push("<span class=\"attr\">" + segment + "</span>");
					} else if (segment.search(CSS_VALUES.reset()) === 0) {
						html.push("<span class=\"value\">" + segment.replace(CSS_COLOUR_FILTER, CSS_COLOUR_SHOW) + "</span>");
					} else {
						html.push(segment);
					};
				});
			} else {
				html.push(segment);
			};
		});

		output = html.join("");
		return (tabWidth) ? output.replaceAll("\t", "  ") : output;
	};


	var SQL_COMMENTS = /\/\*[\s\S]*?\*\/|--.*$\n/gm;
	var SQL_LITERALS = /\b[\d\.]+\b|"([^\\"]|\\.)*"|'([^\\']|\\.)*'/g;
	var SQL_VARIABLES = /(?:@|@@|#|##)[a-z0-9\_]+/gi;
	var SQL_KEYWORDS = /\b(alter|between|create|declare|delete\s+from|distinct|for\sxml\s[a-z]+|from|go|group\s+by|insert\s+into|limit|order\s+by|procedure|select|set|top|updated?|use|view)\b/gi;
	var SQL_LOGIC = /\+|\-|\*|\/|\=|\b(all|and|as|begin|else|end|exec|exists|if|in|is|not|off|on|or|then|union|when|where)\b/gi;
	var SQL_TYPES = /\b(bigint|binary|bit|blob|cursor|date|datetime|decimal|double|enum|float|geography|geometry|image|int|longblob|longtext|n?char|n?text|n?varbinary|n?varchar|numeric|mediumblob|mediumint|mediumtext|smalldatetime|smallint|time|timestamp|tinyblob|tinyint|tinytext|xml)(?:\([^\)]+\))?(?:\s*(?:un)?signed)?/gi;
	var SQL_FUNCTIONS = /\b[a-z0-9\_]+\(/gim;
	var SQL_ALL = new RegExp([
		SQL_COMMENTS.source,
		SQL_LITERALS.source,
		SQL_VARIABLES.source,
		SQL_KEYWORDS.source,
		SQL_LOGIC.source,
		SQL_TYPES.source,
		SQL_FUNCTIONS.source
	].join("|"), "gim");

	function sql(code, tabWidth) {
		var html = [], output;

		code.separate(SQL_ALL).each(function(segment) {
			if (!segment) {
				// do not add blank matches
			} else if (segment.search(SQL_COMMENTS.reset()) === 0) {
				html.push("<span class=\"comment\">" + segment + "</span>");
			} else if (segment.search(SQL_VARIABLES.reset()) === 0) {
				html.push("<span class=\"var\">" + segment + "</span>");
			} else if (segment.search(SQL_KEYWORDS.reset()) === 0) {
				html.push("<span class=\"keyword\">" + segment + "</span>");
			} else if (segment.search(SQL_LOGIC.reset()) === 0) {
				html.push("<span class=\"logic\">" + segment + "</span>");
			} else if (segment.search(SQL_TYPES.reset()) === 0) {
				html.push("<span class=\"type\">" + segment + "</span>");
			} else if (segment.search(SQL_LITERALS.reset()) === 0) {
				html.push("<span class=\"literal\">" + segment + "</span>");
			} else if (segment.search(SQL_FUNCTIONS.reset()) === 0) {
				html.push("<span class=\"func\">" + segment.right(-1) + "</span>(");
			} else {
				html.push(segment);
			};
		});

		output = html.join("");
		return (tabWidth) ? output.replaceAll("\t", "  ") : output;
	};


	var XML_COMMENTS = /(?:<|&lt;)!--[\s\S]*--(?:>|&gt;)/gim;
	var XML_TAGS = /<[a-zA-Z][^><]+>|(?:&lt;).+?(?:&gt;|&lt;)/gm;
	var XML_BRACKETS = /^(?:<|&lt;)|(?:>|&gt;)$/gm;
	var XML_NS_TAGS = /^\/?(\w+:)?(\w+)/gim;
	var XML_NS_TAG = /(\/?)(?:(\w+):)?(\w+)/m;
	var XML_ATTRIBUTES = /\b\w+=/gi;
	var XML_VALUES = /(?:"([^\\"]|\\.)*"|'([^\\']|\\.)*')(?!=)/gi;
	var XML_INITIAL = new RegExp([
		XML_TAGS.source,
		XML_COMMENTS.source
	].join("|"), "gim");
	var XML_SUBS = new RegExp([
		XML_NS_TAGS.source,
		XML_ATTRIBUTES.source,
		XML_VALUES.source
	].join("|"), "gim");

	function xml(code, tabWidth) {
		var html = [], output;
		code.replaceAll("<", "&lt;").replaceAll(">", "&gt;").separate(XML_INITIAL).each(function(segment) {
			if (!segment) {
				// do not add blank matches
			} else if (segment.search(XML_COMMENTS.reset()) === 0) {
				html.push("<span class=\"comment\">" + segment + "</span>");
			} else if (segment.search(XML_TAGS.reset()) === 0) {
				html.push("<span class=\"tag\">&lt;");
				segment.crop(XML_BRACKETS.reset()).separate(XML_SUBS).each(function(segment) {
					if (!segment) {
						// do not add blank matches
					} else if (segment.search(XML_VALUES.reset()) === 0) {
						html.push("<span class=\"value\">" + segment + "</span>");
					} else if (segment.search(XML_ATTRIBUTES.reset()) === 0) {
						html.push("<span class=\"attr\">" + segment.right(-1) + "</span>=");
					} else if (segment.search(XML_NS_TAGS.reset()) === 0) {
						var nstag = segment.match(XML_NS_TAG);
						if (!nstag || nstag.length !== 4) {
							html.push("<span class=\"node\">");
							html.push(segment);
							html.push("</span>");
						} else {
							if (nstag[1]) html.push(nstag[1]);
							if (nstag[2]) {
								html.push("<span class=\"ns\">");
								html.push(nstag[2]);
								html.push("</span>:");
							};
							if (nstag[3]) {
								html.push("<span class=\"node\">");
								html.push(nstag[3]);
								html.push("</span>");
							};
						};
					} else {
						html.push(segment);
					};
				});
				html.push("&gt;</span>");
			} else {
				html.push(segment);
			};
		});
		
		output = html.join("");
		return tabWidth ? output.replaceAll("\t", " ".repeat(tabWidth)) : output;
	};

	// expandable
	var EXPAND_DOCUMENT;
	function EXPAND_ESCAPER(event) {
		var input = event.element();
		if (!["INPUT", "BUTTON", "TEXTAREA"].contains(input.nodeName)) {
			document.query(".expand").each(function(element) {
				if (element.nodeName === "DIV") element.amputate();
				if (element.nodeName === "CODE") element.removeClass("expand");
				else element.removeClass("contract").update("expand");
			});
			document.fumble("keyup", EXPAND_ESCAPER);
			EXPAND_DOCUMENT = null;
		};
	};
	function EXPAND_TOGGLER(event) {
		event.stop();
		var toggler = event.element(), element = toggler.retrieve("owner"), holder = toggler.retrieve("holder");
		toggler.toggleClass("contract");
		if (toggler.hasClass("contract")) {
			var dims = element.dimensions();
			holder.style.height = dims.height + "px";
			element.up().insertAfter(holder, element);
			toggler.update("contract");
		} else {
			holder.amputate();
			toggler.update("expand");
		};
		element.toggleClass("expand");
		if (!EXPAND_DOCUMENT) EXPAND_DOCUMENT = document.handle("keyup", EXPAND_ESCAPER);
	};
	function expandable(element, parent) {
		var coords = $(element).offset();

		var toggler = DOM.create("a", {
			"class": "expand",
			"href": "#/toggle",
			"style": "top:" + coords.top + "px",
			"innerHTML": "expand"
		});
		toggler.store("owner", element);
		toggler.store("holder", DOM.create("div", {
			"class": "expand"
		}));
		toggler.handle("click", EXPAND_TOGGLER);
		element.up().appendChild(toggler);
	};
	return {
		"css": css,
		"xml": xml,
		"javascript": javascript,
		"sql": sql,

		"expandable": expandable
	};
})();
