var DELIMS = " \t\n\r,./<>?;:'\"{}[]\|`~!@#$%^&*()-=+";

var BLOCK_COMMENT_OPEN = "/*";
var BLOCK_COMMENT_CLOSE = "*/";
var LINE_COMMENT_OPEN = "//";
var LINE_COMMENT_CLOSE1 = "\n";
var LINE_COMMENT_CLOSE2 = "\r";

var ESCAPE_CHAR = "\\";   // Ironic... I need to escape this :)

var count = 0;
var KEYWORDS = new Array();
KEYWORDS[count++] = "abstract";
KEYWORDS[count++] = "break";
KEYWORDS[count++] = "byte";
KEYWORDS[count++] = "boolean";
KEYWORDS[count++] = "case";
KEYWORDS[count++] = "catch";
KEYWORDS[count++] = "char";
KEYWORDS[count++] = "class";
KEYWORDS[count++] = "continue";
KEYWORDS[count++] = "default";
KEYWORDS[count++] = "do";
KEYWORDS[count++] = "double";
KEYWORDS[count++] = "else";
KEYWORDS[count++] = "extends";
KEYWORDS[count++] = "false";
KEYWORDS[count++] = "final";
KEYWORDS[count++] = "finally";
KEYWORDS[count++] = "float";
KEYWORDS[count++] = "for";
KEYWORDS[count++] = "if";
KEYWORDS[count++] = "implements";
KEYWORDS[count++] = "import";
KEYWORDS[count++] = "instanceof";
KEYWORDS[count++] = "int";
KEYWORDS[count++] = "interface";
KEYWORDS[count++] = "long";
KEYWORDS[count++] = "native";
KEYWORDS[count++] = "new";
KEYWORDS[count++] = "null";
KEYWORDS[count++] = "package";
KEYWORDS[count++] = "private";
KEYWORDS[count++] = "protected";
KEYWORDS[count++] = "public";
KEYWORDS[count++] = "return";
KEYWORDS[count++] = "short";
KEYWORDS[count++] = "static";
KEYWORDS[count++] = "String";
KEYWORDS[count++] = "super";
KEYWORDS[count++] = "switch";
KEYWORDS[count++] = "synchronized";
KEYWORDS[count++] = "try";
KEYWORDS[count++] = "this";
KEYWORDS[count++] = "threadsafe";
KEYWORDS[count++] = "throw";
KEYWORDS[count++] = "throws";
KEYWORDS[count++] = "transient";
KEYWORDS[count++] = "void";
KEYWORDS[count++] = "volatile";
KEYWORDS[count++] = "while";

// DEPENDENCIES
var inSingleQuotes = false;
var inDoubleQuotes = false;
var inBlockComment = false;
var inLineComment  = false;

/**
 * This is the method you should call from the "onload" event in the
 * body of your HTML page to enable syntax coloring
 */
function highlightKeywords()
{	
	var elements = getElementsByStyleClass("code");
	for( var i = 0; i < elements.length; i++ )
	{
		var html = "";
		var currentWord = "";
		
		var strTok = new StringTokenizer(elements[i].innerHTML);
		while( strTok.hasMoreTokens() )
		{
			var t = strTok.nextToken();
			currentWord = t.getValue();

			// Handle escape sequence if necessary
			currentWord = applyEscapeSequence(currentWord, strTok);

			// Check to see if we are either starting/ending a comment
			currentWord = tagOpenComment(currentWord, strTok);
			currentWord = tagCloseComment(currentWord, strTok);

			// Check to see if we are in quotes
			currentWord = tagQuotes(currentWord);

			// Check to see if this is a keyword
			currentWord = tagKeyword(currentWord);

			// Add the current built string to the html buffer
			html += currentWord;
		}

		elements[i].innerHTML = html;
	}
}

/**
 * This method returns the tagged version of a word assuming
 * that it is a keyword, otherwise it returns just the word
 * with no formatting.
 */
function tagKeyword(currentWord)
{
	return (!inComment() && !inQuotes() && isKeyword(currentWord))
				? ("<b class='keyword'>" + currentWord + "</b>")
				: currentWord;
}

/**
 * This function handles the tagging for syntax coloring when
 * we open a comment. So before the comment, we will tag it
 * with an opening comment tag.
 */
function tagOpenComment(currentWord, strTok)
{
	if( !inComment() && currentWord == "/" && strTok.hasMoreTokens() )
	{
		var next = strTok.nextToken().getValue();
		currentWord += next;
		if( currentWord == BLOCK_COMMENT_OPEN )
		{
			inBlockComment = true;
			currentWord = "<b class='comment'>" + currentWord;
		}
		else if( currentWord == LINE_COMMENT_OPEN )
		{
			inLineComment = true;
			currentWord = "<b class='comment'>" + currentWord;
		}
	}

	return currentWord;
}

/**
 * This function handles the tagging for syntax coloring when
 * a comment is closed (either type). It appends the closing
 * comment tag to the input text so the text will be the last
 * comment-colored item in this comment.
 */
function tagCloseComment(currentWord, strTok)
{
	if( inBlockComment && currentWord == "*" && strTok.hasMoreTokens() )
	{
		// Check to see if we're ending a block comment
		var next = strTok.nextToken().getValue();
		currentWord += next;
		if( currentWord == BLOCK_COMMENT_CLOSE )
		{
			inBlockComment = false;
			currentWord = currentWord + "</b>";
		}
	}
	else if( inLineComment && (currentWord == LINE_COMMENT_CLOSE1 || 
							   currentWord == LINE_COMMENT_CLOSE2) )
	{
		inLineComment = false;
		currentWord = "</b>" + currentWord;
	}

	return currentWord;
}

/**
 * These are just for convenient shorthand when checking both
 * comment flags at the same time for either comments or quotes
 */
function inComment() { return (inLineComment || inBlockComment); }
function inQuotes() { return (inSingleQuotes || inDoubleQuotes); }

/**
 * So we don't trigger any funky tagging on escaped characters,
 * use this to advance the string tokenizer to the appropriate
 * spot. NOTE: This does not validate that it is a valid escape
 * character/word/whatever. It just blindly escapes the next
 * token after the escape character.
 */
function applyEscapeSequence(currentWord, strTok)
{
	if( !inComment() && currentWord == ESCAPE_CHAR )
	{
		currentWord = currentWord + strTok.nextToken().getValue();
	}
	return currentWord;
}

/**
 * This function checks the global flags to see the state of the
 * "colorizer" and determines if the text passed in needs to be
 * tagged for quote coloring.
 */
function tagQuotes(currentWord)
{
	// We ignore quotes found inside of comments (either kind)
	if( inComment() ) 
	{
		return currentWord;
	}

	// Check for double quotes then single quotes
	if( !inSingleQuotes && currentWord == "\"" )
	{
		inDoubleQuotes = !inDoubleQuotes;
		return (inDoubleQuotes ? ("<b class='quotedCode'>" + currentWord)
							   : (currentWord + "</b>"));
	}
	else if( !inDoubleQuotes && currentWord == "'" )
	{
		inSingleQuotes = !inSingleQuotes;
		return (inSingleQuotes ? ("<b class='quotedCode'>" + currentWord)
							   : (currentWord + "</b>"));
	}

	return currentWord;
}


/**
 * Return true if the text passed in is one of our java keywords
 */
function isKeyword(word)
{
	for( var i = 0; i < KEYWORDS.length; i++ )
		if( word == KEYWORDS[i] )
			return true;

	return false;
}


/**
 * Grab all javascript elements of type "div" and return an array
 * of those divs with the appropriate class name (probably "code")
 */
function getElementsByStyleClass(className)
{
	var divs = document.getElementsByTagName('div');
	var elements = new Array();
	for( var e = 0; e < divs.length; e++ )
		if (divs[e].className == className)
			elements[elements.length] = divs[e];

	return elements;
}








/**
 * This is analogous to the StringTokenizer found in java
 * Simply pass it a source string to tokenize and it will
 * split it into tokens. You have no choice, delimiters are
 * returned regardless
 */
function StringTokenizer(source)
{
	// fields
	this.m_source = source;
	this.m_length = source.length;
	this.m_seekPos = 0;

	// methods
	this.nextToken = nextToken;
	this.hasMoreTokens = hasMoreTokens;
	this.isDelim = isDelim;

	function hasMoreTokens()
	{
		return (this.m_seekPos < this.m_length);
	}

	function nextToken()
	{
		buffer = "";
		var flag = false;
		var currentChar = this.m_source.charAt(this.m_seekPos);

		if( isDelim(currentChar) )
		{
			flag = true;
			buffer += currentChar;
			this.m_seekPos++;
		}
		else
		{
			flag = false;

			while( !isDelim(currentChar) )
			{
				buffer += currentChar;
				currentChar = this.m_source.charAt(++this.m_seekPos);
			}
		}

		return new Token(buffer, flag);
	}
	
	function isDelim(ch)
	{
		return (DELIMS.indexOf(ch) != -1);
	}
}

/**
 * An instance of the Token class is what is returned
 * during a call to StringTokenizer.nextToken()
 */
function Token(value, isDelim)
{
	function getIsDelim() { return this.m_isDelim; }
	function getValue() { return this.m_value; }

	this.m_isDelim = isDelim;
	this.m_value = value;

	this.getIsDelim = getIsDelim;
	this.getValue = getValue;
}