/*
 * @description This script hightlightes Javascript code found in specified elements. The color convention are taken from
 * JSEclipse.
 * @author smnh
 */

new function() {
// Could make external CSS file, but it will decentralize the feature. 
var styles = { 	"singlelineComment" : "color:#808000;",
				"regularExpression"	: "color:#B40000;",
				"multilineComment" 	: "color:#328032;",
				"string"			: "color:#008000;",
				"reservedWords"		: "color:#0000C8; font-weight:bold;",
				"otherWords"		: "color:#0064C8;",
				"constants"			: "color:#C86400; font-weight:bold;",
				"bubblesAPI"		: "color:#0064C8;" }

var bubblesAPI = {	"SSB":			"http://bubbleshq.com/api_jsdoc/symbols/SSB.html",
					"console":		"http://bubbleshq.com/api_jsdoc/symbols/SSB.console.html",
					"contextMenu":	"http://bubbleshq.com/api_jsdoc/symbols/SSB.contextMenu.html",
					"dragDrop":		"http://bubbleshq.com/api_jsdoc/symbols/SSB.dragDrop.html",
					"prefs":		"http://bubbleshq.com/api_jsdoc/symbols/SSB.prefs.html",
					"pane":			"http://bubbleshq.com/api_jsdoc/symbols/SSB.prefs.pane.html" };
					
var reserverWords = new Array(	"break",	"continue",	"do",		"for",		"import",
								"new",		"this",		"void",		"case",		"default",
								"else",		"function",	"in",		"return",	"typeof",
								"while",	"comment",	"delete",	"export", 	"if",
								"label",	"switch",	"var",		"with"	);

var otherWords = new Array(	"alert",				"eval",					"Link",					"outerHeight",		"scrollToAnchor",
							"FileUpload",			"location",				"outerWidth",			"SelectArea",		"find",
							"Location",				"Packages",				"selfarguments",		"focus",			"locationbar",
							"pageXoffset",			"setIntervalArray",		"Form",					"Math",				"pageYoffset",
							"setTimeoutassign",		"Frame",				"menubar",				"parent",			"statusblur",
							"frames",				"MimeType",				"parseFloat",			"statusbarBoolean",	"Function",
							"moveBy",				"parseInt",				"stopButton",			"getClass",			"moveTo",
							"Password",				"Stringcallee",			"Hidden",				"name",				"personalbar",
							"Submitcaller",			"history",				"NaN",					"Plugin",			"suncaptureEvents",
							"History",				"navigate",				"print",				"taintCheckbox",	"home",
							"navigator",			"prompt",				"TextclearInterval",	"Image",			"Navigator",
							"prototype",			"TextareaclearTimeout",	"Infinity",				"netscape",			"Radio",
							"toolbarclose",			"innerHeight",			"Number",				"ref",				"topclosed",
							"innerWidth",			"Object",				"RegExp",				"toStringconfirm",	"isFinite",
							"onBlur",				"releaseEvents",		"unescapeconstructor",	"isNan",			"onError",
							"Reset",				"untaintDate",			"java",					"onFocus",			"resizeBy",
							"unwatchdefaultStatus",	"JavaArray",			"onLoad",				"resizeTo",			"valueOfdocument",
							"JavaClass",			"onUnload",				"routeEvent",			"watchDocument",	"JavaObject",
							"open",					"scroll",				"windowElement",		"JavaPackage",		"opener",
							"scrollbars",			"Windowescape",			"length",				"Option","scrollBy"	);

var constants = new Array("true", "false", "null");

// Start this script after the page have been loaded.
if (window.addEventListener){
	window.addEventListener('load', init, false); 
} else if (window.attachEvent){
	window.attachEvent('onload', init);
}

function init() {
	// get scrollBoxes elements.
	var scrollBoxesArray = getElementsByClassName("scrollbox", document);
	
	// iterate through all scrollBoxes
	for (var i = 0; i < scrollBoxesArray.length; i++) {
		var scrollBox = scrollBoxesArray[i];
		
		// get inner <pre> element.
		var pre = firstChildElement(scrollBox);
		if (pre.tagName.toLowerCase() != "pre")
			continue;
	
		// highlight the JavaScript
		highlightJavaScriptCode(pre);
	}
}

function highlightJavaScriptCode(element) {
	var html = element.innerHTML;
	var newHtml = "";
	var code = "";
	var startIndex = 0;
	// This regular expression finds all none code strings - JavaScript strings and different
	// comments.
	// \/\/.* = matches singleline comments
	// \/\*[\s\S]*?\*\/ = matches multiline comments
	// \/[^\/\*](\\.|[^\/\\])*\/ = matches regular expressions with escaped slash.
	// "(\\.|[^"\\])*" = matches double quoted string with escaped double quotes.
	// '(\\.|[^'\\])*' = matches single quoted string with escaped single quotes.
	var noneCodeRegExp = /\/\/.*|\/\*[\s\S]*?\*\/|\/[^\/\*](\\.|[^\/\\])*\/|"(\\.|[^"\\])*"|'(\\.|[^'\\])*'/g;
	var nonCodeResult;
	/* Iterate through all none code strings that found. On each iteration highlight all code
	 * that is preceding these strings. Then highlight the none code string and continue to next
	 * iteration. 
	 */
	while ( (nonCodeResult = noneCodeRegExp.exec(html)) != null ) {
		// Extract code that is preceding none code strings.
		code = html.substring(startIndex, nonCodeResult.index);
		// Highlight code that is preceding none code strings.
		newHtml += highlightCode(code);
		// Change the start index for the next iteration.
		startIndex = noneCodeRegExp.lastIndex;

		// Highlight the none code strings
		var nonCode = nonCodeResult[0];
		
		// single line comments - //
		if (/^\/\//.test(nonCode)) {
			newHtml += '<span style="'+styles.singlelineComment+'">'+nonCode+'</span>';
		}
		
		// multiline comments - /* */ , /** */
		if (/^\/\*[\s\S]*?\*\//.test(nonCode)) {
			newHtml += '<span style="'+styles.multilineComment+'">'+nonCode+'</span>';
		}
		
		// regular expression
		if (/^\/[^\/\*](\\.|[^\/\\])*\//.test(nonCode)) {
			newHtml += '<span style="'+styles.regularExpression+'">'+nonCode+'</span>';
		}

		// strings " ", ' '
		if (/^"(\\.|[^"\\])*"|^'(\\.|[^'\\])*'/.test(nonCode)) {
			newHtml += '<span style="'+styles.string+'">'+nonCode+'</span>';
		}
	}
	// Hightlight the last code part.
	code = html.substring(startIndex);
	newHtml += highlightCode(code);
	
	// IE bug, innerHTML in IE normalizes HTML:
	// http://www.quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
	// http://www.stevekallestad.com/blog/innerhtml_in_ie_with_the_pre_tag.html
	if ("outerHTML" in element) {
		// Extract opening and closing tag and then rewrite Html using outerHTML
		var outerHtml = element.outerHTML;
		var openTag = /^<[^>]+>/.exec(outerHtml);
		var closeTag = /<\/[^<]+>$/.exec(outerHtml)[0];
		element.outerHTML = openTag + newHtml + closeTag;
	}
	else {
		// This is not IE, hence no bug, just use innerHTML.
		element.innerHTML = newHtml;
	}
}

function highlightCode(code) {
	var html = code;
	
	// highlight reserved words
	for (var i = 0; i < reserverWords.length; i++) {
		var regExp = new RegExp("(\\b"+reserverWords[i]+"\\b)","g");
		html = html.replace(regExp, '<span style="'+styles.reservedWords+'">$1</span>');
	}
	
	// highlight other words
	for (var i = 0; i < otherWords.length; i++) {
		var regExp = new RegExp("(\\b"+otherWords[i]+"\\b)","g");
		html = html.replace(regExp, '<span style="'+styles.otherWords+'">$1</span>');
	}
	
	// highlight constants
	for (var i = 0; i < constants.length; i++) {
		var regExp = new RegExp("(\\b"+constants[i]+"\\b)","g");
		html = html.replace(regExp, '<span style="'+styles.constants+'">$1</span>');
	}
	
	// highlight bubbles API
	for (object in bubblesAPI) {
		var regExp = new RegExp("(\\b"+object+"\\b)","g");
		html = html.replace(regExp, '<a href="'+bubblesAPI[object]+'" style="'+styles.bubblesAPI+'">$1</a>');
	}
	
	return html;
}

/*
 * Function that receives an element node as a parameter and returns first child element node of that element.
 * If the element does not have child elements node the method returns null.
 */
function firstChildElement(element) {
	for (var i = 0; i < element.childNodes.length; i++)
		if ( element.childNodes[i].nodeType == 1 )
			return element.childNodes[i];
	return null;
}

/**
 * Gets elements by their class names
 * @param {String} className The class name to search. Can be complex like "title bold", then it will
 * find all elements that have "title" and "bold" classes. Can also be a regular expression.
 * @param {String} parentElement The ancestor element in which to search for elements.
 * @return {Array} elements Array of element nodes that matched the class name.
 */
function getElementsByClassName(aClassName, parentElement) {
	var className = aClassName.replace(/^\s+/, "").replace(/\s+$/, "");
	if (className == "") return null;
	var classNamesArray = className.split(/\s+/);
	var elements = new Array();
	if (!parentElement)
		parentElement = document;
	try {
		elements = traverseSiblings(parentElement);
	} catch(e) {
		myAlert("Bubble script error in traverseSiblings(): " + e.name + " - " + e.message);
	} finally {
		return elements;
	}
	
	/**
	 * Private recursy function for traversing node tree.
	 */
	function traverseSiblings(parentElement) {
		var elements = new Array();
		var child = null;
		var hasClass = null;
		var regexp = null;
		for (var i = 0; i < parentElement.childNodes.length; i++) {
			child = parentElement.childNodes[i];
			if ( child.nodeType == 1 ) {
				hasClass = true;
				for (var j = 0; j < classNamesArray.length && hasClass; j++) {
					regexp = new RegExp("\\b"+classNamesArray[j]+"\\b");
					if (!regexp.test(child.className)) {
						hasClass = false;
					}
				}
				if (hasClass) {
					elements.push(child);
				}
				elements = elements.concat( traverseSiblings(child) );
			}
		}
		return elements;
	}
}

}