// Set up events
addEvent(window, 'DOMContentLoaded', init);

// ===== FUNCTIONS ===== //
function init()
{
	enhance_codeblocks();
}

function enhance_codeblocks()
{
	// --- Set up rulesets --- //
	// DesktopX Namespace
	var rulesDesktopX = new Code_Rules('dx', true);
	rulesDesktopX.rules = new Array(
										'Object',
										'DesktopX',
										'Script',
										'Widget',
										'System'
									);

	// VBScript
	var vbRulesKeywords = new Code_Rules('keyword', false);
	vbRulesKeywords.rules = new Array(
										'end',
										'function',
										'if',
										'dim',
										'set',
										'getref',
										'not',
										'sub',
										'(',
										')'
									);
	var vbRulesSymbols = new Code_Rules('symbol', false);
	vbRulesSymbols.rules = new Array(
										'true',
										'false',
										'null',
										'=',
										'&',
										'.',
										','
									);
	var vbRules = new Array(vbRulesKeywords, vbRulesSymbols, rulesDesktopX);

	// JScript Namespace
	var jsRulesKeywords = new Code_Rules('keyword', false);
	jsRulesKeywords.rules = new Array(
										'end',
										'function',
										'if',
										'var',
										'for',
										'while',
										'in',
										'return',
										'switch',
										'case',
										'break',
										'default',
										'this',
										'new',
										'prototype',
										'(',
										')',
										'{',
										'}',
										';',
										'[',
										']',
										':',
										'?',
										',',
										'&&',
										'||',
										'=='
									);
	var jsRulesObjects = new Code_Rules('object', true);
	jsRulesObjects.rules = new Array(
										'ActiveXObject',
										'Array',
										'Boolean',
										'Debug',
										'Date',
										'Enumerator',
										'Error',
										'Math',
										'Number',
										'Object',
										'RegExp',
										'String',
										'VBArray'
									);
	var jsRulesSymbols = new Code_Rules('symbol', false);
	jsRulesSymbols.rules = new Array(
										'true',
										'false',
										'null',
										'NaN',
										'.',
										'=',
										'&',
										'|',
										'+',
										'-',
										'*',
										'/',
										'<',
										'>',
										'!'
									);
	var jsRules = new Array(jsRulesKeywords, jsRulesObjects, jsRulesSymbols, rulesDesktopX);


	// Iterate though all code elements and
	// perform appropriate action to enhance them
	var codeblocks = new Array();
	var codes = $$('code');
	var code;

	// We must first build an array with refereances because
	// the as we replace the CODE elements the 'codes' index
	// changes as well
	for (var i = 0; i < codes.length; i++)
	{
		code = codes[i];
		// Check if it's a codeblock (parent to a PRE)
		if (code.parentNode.nodeName.toLowerCase() == 'pre')
		{
			codeblocks.push(code);
		}
	} // for

	// Now we go through all the code blocks and process them
	for (var i = 0; i < codeblocks.length; i++)
	{
		code = codeblocks[i];
		switch(code.className)
		{
			case 'vbscript':
				highlighter = new Code_Hightlighter(code, vbRules);
				highlighter.commentSingleLine = '\'';
				highlighter.stringChars = '"';
				highlighter.parse();
				break;
			case 'jscript':
				highlighter = new Code_Hightlighter(code, jsRules);
				highlighter.commentSingleLine = '//';
				highlighter.commentMultiLine  = '/*|*/';
				highlighter.stringChars = '\'"';
				highlighter.escapeChar = '\\';
				highlighter.regex = '/';
				highlighter.parse();
				break;
			default:
				highlighter = new Code_Hightlighter(code, new Code_Rules('', false));
				highlighter.parse();
		} // switch
	} // for
}


// === Code Highlight === //
function Code_Hightlighter(codeElement, codeRules)
{
	this.rulesets = codeRules;
	this.codeElement = codeElement;

	//this.codeText = html_specialchars_decode( codeElement.innerHTML.trim().replace(/\t/g, '    ') );
	this.codeText = html_specialchars_decode( codeElement.childNodes[0].nodeValue );

	// Get rid of excess whitespace at the beginning and end
	this.codeText = this.codeText.trim();
	// Convert tabs to spaces
	//this.codeText = this.codeText.replace(/\t/g, '&nbsp;&nbsp;&nbsp;&nbsp;')
	// Convert spaces to non-breaking spaces (IE)
	//this.codeText = this.codeText.replace(/ /g, '&nbsp;')
	// Make linebreaks uniform
	this.codeText = this.codeText.trim().replace(/\n\r|\r\n|\r/g, '\n');
	// Split each line into an array;
	this.lines    = this.codeText.split('\n');

	this.seekCommentMultiline = false;
}
Code_Hightlighter.prototype.commentSingleLine = null;
Code_Hightlighter.prototype.commentMultiLine = null;
Code_Hightlighter.prototype.stringChars = null;
Code_Hightlighter.prototype.escapeChar = null;
Code_Hightlighter.prototype.regex = null;
// === Code Highlight Parse === //
Code_Hightlighter.prototype.parse = function()
{
	// Create codeblock list
	//var codeblock = document.createElement('ol');
	//codeblock.className = "codeblock " + this.codeElement.className;

	var c = document.createElement('div');
	c.className = "codeblock " + this.codeElement.className;

	// Toolbar
	var toolbar = document.createElement('div');
	toolbar.className = 'toolbar';
	c.appendChild(toolbar);

	// Create Titlebar text
	var codeLanguage = 'Code';
	switch(this.codeElement.className)
	{
		case 'jscript':  codeLanguage = 'Javascript'; break;
		case 'vbscript': codeLanguage = 'VBScript';   break;
	} // switch
	var title = document.createElement('span');
	title.appendChild( document.createTextNode(codeLanguage) );
	title.className = 'title';

	// Add link to enable/disable colouring
	var colouring = document.createElement('a');
	colouring.appendChild( document.createTextNode('Disable Colouring') );
	colouring.href = '#';
	colouring.className = 'button';
	colouring.onclick = function()
	{
		var pre       = this.parentNode.parentNode.getElementsByTagName('pre')[0];
		var codeblock = this.parentNode.parentNode.getElementsByTagName('ol')[0];

		if (pre.style.display == 'block')
		{
			pre.style.display = 'none';
			codeblock.style.display = 'block';
			this.innerHTML = 'Disable Colouring';
		}
		else
		{
			pre.style.display = 'block';
			codeblock.style.display = 'none';
			this.innerHTML = 'Enable Colouring';
		}
		return false;
	};
	toolbar.appendChild(colouring);

	// Add Copy function if supported
	if (window.clipboardData)
	{
		var copy = document.createElement('a');
		copy.appendChild( document.createTextNode('Copy') );
		copy.href = '#';
		copy.className = 'button';
		copy.onclick = function()
		{
			var code = this.parentNode.parentNode.getElementsByTagName('code')[0];
			var codeText = code.childNodes[0].nodeValue.replace(/\n\r|\r\n|\r/g, '\r\n');
			window.clipboardData.setData("Text", codeText);
			return false;
		};
		toolbar.appendChild(copy);
	}

	// Add titlebar text
	toolbar.appendChild(title);

	// Codeblock
	var codeblock = document.createElement('ol');
	c.appendChild(codeblock);

	//console.log(this.codeElement.className, this.commentSingleLine);

	// Parse lines
	var li;
	//alert(this.lines.length);
	for (var i = 0; i < this.lines.length; i++)
	{
		//alert('i' + i + '\n' + this.lines[i].length + '\n\n' + this.lines[i]);
		li = document.createElement('li');
		formattedLine = this.formatLine(this.lines[i]);
		li.innerHTML = formattedLine;
		codeblock.appendChild( li );
	}

	// Insert into document by replacing the old code element
	//pre.parentNode.replaceChild(codeblock, pre);
	var pre = this.codeElement.parentNode;
	pre.parentNode.insertBefore(c, pre);
	c.appendChild(pre);
	pre.style.display = 'none';
}
// === Code Highlight Format Line === //
Code_Hightlighter.prototype.formatLine = function(line)
{
	var SEEK_NORMAL     = 0;
	var SEEK_COMMENT_SL = 1;
	var SEEK_COMMENT_ML = 2;
	var SEEK_STRING     = 3;
	var SEEK_REGEX      = 4;

	var offset = 0
	var word, char;
	var seekMode = SEEK_NORMAL;
	var formattedLine = '';
	var formattedWord;
	var commentSniffer, stringSniffer;
	var stringChar;
	var escape = false;
	var needle;

	if (!line.length)
	{
		line = ' ';
	}

	if (this.commentMultiLine != null)
	{
		var commentStart = this.commentMultiLine.split('|')[0];
		var commentEnd   = this.commentMultiLine.split('|')[1];
	}

	if (this.seekCommentMultiline)
	{
		seekMode = SEEK_COMMENT_ML;
	}

	for (var i = 0; i < line.length; i++)
	{
		char = line.charAt(i);

		switch(seekMode)
		{
			case SEEK_NORMAL:
				//commentSnifferSL = (this.commentSingleLine == null) ? '' : this.commentSingleLine.charAt(0);
				commentSnifferML = (this.commentMultiLine == null)  ? '' : commentStart.charAt(0);

				// Look for string
				if (this.stringChars != null && this.stringChars.indexOf(char) != -1)
				{
					//console.log('String Start Found...');
					stringChar = char;
					seekMode = SEEK_STRING;
					offset = i;
					break;
				}

				// Look for single line comment
				if (this.commentSingleLine != null && line.substr(i, this.commentSingleLine.length) == this.commentSingleLine)
				{
					//console.log('--Found!');
					formattedLine += '<span class="comment">' + html_specialchars_encode(line.substr(i)) + '</span>';
					seekMode = SEEK_COMMENT_SL;
					offset = line.length;
					break;
				}

				// Look for multi line comment
				if (this.commentMultiLine != null && line.substr(i, commentStart.length) == commentStart)
				{
					//console.log('Comment ML Start');
					seekMode = SEEK_COMMENT_ML;
					offset = i;
					break;
				}

				// Look for regex
				if (this.regex != null && line.substr(i, this.regex.length) == this.regex)
				{
					//console.log('Regexp Start');
					seekMode = SEEK_REGEX;
					offset = i;
					break;
				}

				switch(char)
				{
					case ' ':
					case '.':
					case ',':
					case '(':
					case ')':
					case '{':
					case '}':
					//case '|':
					case '!':
					//case '=':
					case ';':
					case ':':
					//case '&':
					case '[':
					case ']':
					case '+':
					case '-':
					case '+':
					case '<':
					case '>':
					case '*':
					//case '/':
						word = line.substring(offset, i);
						offset = i + 1;
						formattedLine += this.formatWord(word) + this.formatWord(char);
						break;
				} // switch;
				break;
			case SEEK_COMMENT_ML:
				if (line.substr(i - commentEnd.length + 1, commentEnd.length) == commentEnd)
				{
					//console.log('--MC End Found!');
					formattedLine += '<span class="comment">' + html_specialchars_encode(line.substring(offset, i + 1)) + '</span>';
					seekMode = SEEK_NORMAL;
					this.seekCommentMultiline = false;
					offset = i + 1;
				}
				break;
			case SEEK_STRING:
				if (char == stringChar && !escape)
				{
					//console.log('String End');
					formattedLine += '<span class="string">' + html_specialchars_encode(line.substring(offset, i + 1)) + '</span>';
					seekMode = SEEK_NORMAL;
					offset = i + 1;
				}
				if (this.escapeChar != null && char == this.escapeChar && !escape)
				{
					escape = true;
				}
				else
				{
					escape = false;
				}
				break;
			case SEEK_REGEX:
				if (line.substr(i - this.regex.length + 1, this.regex.length) == this.regex)
				{
					// Check for escape character
					if (line.substr(i - this.regex.length, 1) != '\\')
					{
						//console.log('--REGEXP End Found!');
						formattedLine += '<span class="regexp">' + html_specialchars_encode(line.substring(offset, i + 1)) + '</span>';
						seekMode = SEEK_NORMAL;
						offset = i + 1;
					}
				}
				break;
		} // switch
	}
	if (seekMode == SEEK_COMMENT_ML)
	{
		formattedLine += '<span class="comment">' + html_specialchars_encode(line.substr(offset)) + '</span>';
		this.seekCommentMultiline = true;
	}
	else
	{
		formattedLine += this.formatWord(line.substr(offset));
	}

	return formattedLine;
}
Code_Hightlighter.prototype.formatWord = function(word)
{
	// Check if it's a number
	if (word.length && word != ' ' && !isNaN(word))
	{
		return '<span class="number">' + word + '</span>';
	}


	var formattedWord = html_specialchars_encode(word);
	var rules;

	//console.log("Formatting Word: '%s'", word);

	// Loop through all the rule sets
	for (var j = 0; j < this.rulesets.length; j++)
	{
		rules = this.rulesets[j];
		// For each ruleset, match up all the words in the array
		for (var i = 0; i < rules.rules.length; i++)
		{
			if ( (rules.strict && rules.rules[i] == word) || (!rules.strict && rules.rules[i] == word.toLowerCase()))
			{
				formattedWord = '<span class="' + rules.className + '">' + formattedWord + '</span>';
			}
		}
	}

	return formattedWord;
}

// === Rule Set === //
function Code_Rules(className, boolStrict)
{
	this.className = className;
	this.strict    = boolStrict;
	this.rules     = new Array();
}


function html_specialchars_encode(string)
{
	return string.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/ /g, '&nbsp;');
	//return string.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function html_specialchars_decode(string)
{
	return string.replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');
	//return string.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
}