/* Javascript Syntax Highlighter
 * Highlights many different programming languages
 * Outputs valid XHTML, so can be used in XHTML pages!
 * Written by: Michiel van Eerd
 * 16/16/2007: Beginning to write it...
 */
 
 /* IE doesn't understand indexOf() on arrays, so add it */
if (!Array.prototype.indexOf) {
        Array.prototype.indexOf = function(val) {
                var len = this.length;
                for (var i = 0; i < len; i++) {
                        if (this[i] == val) return i;
                }
                return -1;
        }
}

function Language(){};

function Highlighter(withLineNumbers)
{
	var xhtmlEntities = {
		"&" : "&amp;",
		"<" : "&lt;",
		">" : "&gt;"
	};
	
	this.init = function()
	{
		var codeList = document.getElementsByTagName("code");
		for (var i = 0, len = codeList.length; i < len; i++)
		{
			if (codeList[i].className && Language[codeList[i].className])
			{
				doHighlight(codeList[i]);
			}
		}
	};
	
	// Herschrijft de gehele string naar valide XHTML, retourneert de string en extra lengte t.o.v. originele string
	function strToXHTML(str)
	{
		var addLen = 0;
		
		for (var key in xhtmlEntities)
		{
			str = str.replace(new RegExp(key, "g"),
				function(match, offset, s)
				{
					addLen += (xhtmlEntities[key].length - 1);
					return xhtmlEntities[key];
				}
			);
		}
		return {
			"str" : str,
			"addLen" : addLen
		}
	}
	
	function copyObject(ob) {
		var newOb = {};
		for (var prop in ob) {
			newOb[prop] = ob[prop];
		}
		return newOb;
	}
	
	function doHighlight(codeEl)
	{
		var lang = Language[codeEl.className];
		
		var str = "";
		for (var i = 0; i < codeEl.childNodes.length; i++)
		{
			str += codeEl.childNodes[i].data; // data rewrites HTML entities to real characters
		}
		var lines = (str.indexOf("\n") != -1) ? str.split("\n") : str.split("\r");
		
		var beginMultiCommentIndex = -1;
		
		forLineLoop:
		for (var lineIndex = 0, lineCount = lines.length; lineIndex < lineCount; lineIndex++)
		{
			var line = lines[lineIndex];
			var prop = {};
			
			forCharLoop:
			for (var charIndex = 0, lineLen = line.length; charIndex < lineLen; charIndex++)
			{
				var c = line.charAt(charIndex);
				
				// End multiline comment
				if (beginMultiCommentIndex != -1)
				{
					var endMultiCommentIndex = -1;
					for (; charIndex < lineLen; charIndex++)
					{
						c = line.charAt(charIndex);
						if (c == "\\")
						{
							charIndex++;
							continue;
						}
						if (c == lang.comment.multi.end.charAt(0))
						{
							endMultiCommentIndex = charIndex;
							for (var i = 0; i < lang.comment.multi.end.length; i++)
							{
								if (line.charAt(charIndex + i) != lang.comment.multi.end.charAt(i))
								{
									endMultiCommentIndex = -1;
									break;
								}
							}
							if (endMultiCommentIndex != -1)
							{
								charIndex += (lang.comment.multi.end.length - 1);
								endMultiCommentIndex = charIndex;
								break;
							}
						}
					}
					var realEndIndex = (endMultiCommentIndex != -1) ? endMultiCommentIndex : lineLen - 1;
					var addLen = "<span class='comment'></span>".length;
					var substrOb = strToXHTML(line.substr(beginMultiCommentIndex, realEndIndex - beginMultiCommentIndex + 1));
					line = line.substr(0, beginMultiCommentIndex) + "<span class='comment'>" + substrOb.str + "</span>" + line.substr(realEndIndex + 1);
					charIndex += (addLen + substrOb.addLen);
					lineLen += (addLen + substrOb.addLen);
					prop[beginMultiCommentIndex] = addLen + substrOb.str.length;
					beginMultiCommentIndex = (endMultiCommentIndex != -1) ? -1 : 0;
					continue forCharLoop;
				}
				
				// Begin multiline comment
				if (lang.comment.multi && c == lang.comment.multi.start.charAt(0))
				{
					beginMultiCommentIndex = charIndex;
					for (var i = 0; i < lang.comment.multi.start.length; i++)
					{
						if (line.charAt(charIndex + i) != lang.comment.multi.start.charAt(i))
						{
							beginMultiCommentIndex = -1;
							break;
						}
					}
					if (beginMultiCommentIndex != -1)
					{
						charIndex += lang.comment.multi.start.length - 1;
						if (charIndex == lineLen - 1)
						{
							charIndex--;
						}
						continue forCharLoop;
					}
				}
				
				// Single comment
				if (lang.comment.single && c == lang.comment.single.start.charAt(0))
				{
					var beginSingleCommentIndex = charIndex;
					// Eventueel het begin van een single comment
					for (var i = 0; i < lang.comment.single.start.length; i++)
					{
						if (line.charAt(charIndex + i) != lang.comment.single.start.charAt(i))
						{
							beginSingleCommentIndex = -1
							break;
						}
					}
					if (beginSingleCommentIndex != -1)
					{
						// Alles totaan einde van de regel is comment
						var substrOb = strToXHTML(line.substr(beginSingleCommentIndex));
						var addLen = "<span class='comment'></span>".length;
						line = line.substr(0, beginSingleCommentIndex) + "<span class='comment'>" + substrOb.str + "</span>";
						charIndex = line.length - 1;
						prop[beginSingleCommentIndex] = addLen + substrOb.str.length;
						continue forCharLoop;
					}
				}
				
				// Quotes
				if (c == "\"" || c == "'")
				{
					var quote = c;
					var beginQuoteIndex = charIndex;
					// Hier doorgaan naar einde quote
					for (charIndex += 1; charIndex < lineLen; charIndex++)
					{
						c = line.charAt(charIndex);
						if (c == "\\")
						{
							charIndex++;
							continue;
						}
						if (c == quote)
						{
							// Einde
							var substrOb = strToXHTML(line.substr(beginQuoteIndex, charIndex - beginQuoteIndex + 1));
							var addLen = "<span class='quote'></span>".length;
							line = line.substr(0, beginQuoteIndex) + "<span class='quote'>" + substrOb.str + "</span>" + line.substr(charIndex + 1);
							prop[beginQuoteIndex] = addLen + substrOb.str.length;
							charIndex += (addLen + substrOb.addLen);
							lineLen += (addLen + substrOb.addLen);
							continue forCharLoop;
						}
					}
				}
				
				// Operators
				if (lang.operator.indexOf(c) != -1)
				{
					c = (xhtmlEntities[c]) ? xhtmlEntities[c] : c;
					var addLen = "<span class='operator'></span>".length + (c.length - 1);
					line = line.substr(0, charIndex) + "<span class='operator'>" + c + "</span>" + line.substr(charIndex + 1);
					prop[charIndex] = addLen + c.length;
					charIndex += addLen;
					lineLen += addLen;
					continue forCharLoop;
				}
			}
			
			// Keywords - not for each char, but each line
			for (var i = 0; i < lang.keyword.length; i++)
			{
				var keyword = lang.keyword[i];
				var keywordIndex = line.indexOf(keyword);
				while (keywordIndex != -1)
				{
					if (/\w/.test(line.charAt(keywordIndex - 1)) || /\w/.test(line.charAt(keywordIndex + keyword.length))) {
						keywordIndex = line.indexOf(keyword, keywordIndex + 1);
						continue;
					}
					
					var isKeyword = true;
					for (var key in prop) {
						if (keywordIndex >= parseInt(key) && keywordIndex < (parseInt(key) + parseInt(prop[key]))) {
							isKeyword = false;
							break;
						}
					}
					if (isKeyword) {
						var addString = "<span class='keyword'></span>";
						var addLen = addString.length; // dit is erbij gekomen
						line = line.substr(0, keywordIndex) + "<span class='keyword'>" + keyword + "</span>" + line.substr(keywordIndex + keyword.length);
						prop[keywordIndex] = keyword.length + addLen;
						var tmpOb = new Object();
						for (var key in prop) {
							if (parseInt(key) > keywordIndex) {
								var newIndex = parseInt(key) + addLen;
								tmpOb[newIndex] = prop[key];
							} else {
								tmpOb[key] = prop[key];
							}
						}
						prop = copyObject(tmpOb);
						keywordIndex = line.indexOf(keyword, keywordIndex + addLen + keyword.length);
					} else {
						keywordIndex = line.indexOf(keyword, keywordIndex + 1);
					}
					
				}
			}
			
			//line.prop = prop;
			lines[lineIndex] = line;
		}
		
		// Print the lines
		var joinString = "";
		var withLineNumbersThisTime = withLineNumbers;
		var newLines = null;
		
		if (codeEl.parentNode.nodeName.toLowerCase() != "pre")
		{
			withLineNumbersThisTime = false;
		}
		
		if (withLineNumbersThisTime)
		{
			newLines = ["<ol>"];
			for (var i = 0; i < lineCount; i++) {
				newLines.push("<li><span>" + lines[i] + "</span> </li>");
			}
			newLines.push("</ol>");
		}
		else
		{
			newLines = lines;
			if (codeEl.parentNode.nodeName.toLowerCase() == "pre")
			{
				joinString = "\n";
			}
		}
		
		if (codeEl.outerHTML && codeEl.parentNode.nodeName.toLowerCase() == "pre")
		{
			codeEl.outerHTML = "<pre><code class='" + codeEl.className + "'>" + newLines.join("\r") + "</code></pre>"
		}
		else
		{
			codeEl.innerHTML = newLines.join(joinString);
		}
	}
	
};
