/*
 * SHJS - Syntax Highlighting in JavaScript Copyright (C) 2007
 * gnombat@users.sourceforge.net License:
 * http://shjs.sourceforge.net/doc/license.html
 */

function sh_highlightString(inputString, language, builder) {
	var patternStack = {
		_stack : [],
		getLength : function() {
			return this._stack.length;
		},
		getTop : function() {
			var stack = this._stack;
			var length = stack.length;
			if (length === 0) {
				return undefined;
			}
			return stack[length - 1];
		},
		push : function(state) {
			this._stack.push(state);
		},
		pop : function() {
			if (this._stack.length === 0) {
				throw "pop on empty stack";
			}
			this._stack.pop();
		}
	};

	// the current position within inputString
	var pos = 0;

	// the name of the current style, or undefined if there is no current style
	var currentStyle = undefined;

	var output = function(s, style) {
		var length = s.length;
		// this is more than just an optimization - we don't want to output
		// empty <span></span> elements
		if (length === 0) {
			return;
		}
		if (!style) {
			var pattern = patternStack.getTop();
			if (pattern !== undefined && !('state' in pattern)) {
				style = pattern.style;
			}
		}
		if (currentStyle !== style) {
			if (currentStyle) {
				builder.endElement();
			}
			if (style) {
				builder.startElement(style);
			}
		}
		builder.text(s);
		pos += length;
		currentStyle = style;
	};

	var endOfLinePattern = /\r\n|\r|\n/g;
	endOfLinePattern.lastIndex = 0;
	inputString=inputString.trim();
	var inputStringLength = inputString.length;
	while (pos < inputStringLength) {
		var start = pos;
		var end;
		var startOfNextLine;
		var endOfLineMatch = endOfLinePattern.exec(inputString);
		if (endOfLineMatch === null) {
			end = inputStringLength;
			startOfNextLine = inputStringLength;
		} else {
			end = endOfLineMatch.index;
			startOfNextLine = endOfLinePattern.lastIndex;
		}

		var line = inputString.substring(start, end);

		var matchCache = null;
		var matchCacheState = -1;
		for (;;) {
			var posWithinLine = pos - start;
			var pattern = patternStack.getTop();
			var stateIndex = pattern === undefined ? 0 : pattern.next;
			var state = language[stateIndex];
			var numPatterns = state.length;
			if (stateIndex !== matchCacheState) {
				matchCache = [];
			}
			var bestMatch = null;
			var bestMatchIndex = -1;
			for (var i = 0; i < numPatterns; i++) {
				var match;
				if (stateIndex === matchCacheState
						&& (matchCache[i] === null || posWithinLine <= matchCache[i].index)) {
					match = matchCache[i];
				} else {
					var regex = state[i].regex;
					regex.lastIndex = posWithinLine;
					match = regex.exec(line);
					matchCache[i] = match;
				}
				if (match !== null
						&& (bestMatch === null || match.index < bestMatch.index)) {
					bestMatch = match;
					bestMatchIndex = i;
				}
			}
			matchCacheState = stateIndex;

			if (bestMatch === null) {
				output(line.substring(posWithinLine), null);
				break;
			} else {
				// got a match
				if (bestMatch.index > posWithinLine) {
					output(line.substring(posWithinLine, bestMatch.index), null);
				}

				pattern = state[bestMatchIndex];

				var newStyle = pattern.style;
				var matchedString;
				if (newStyle instanceof Array) {
					for (var subexpression = 0; subexpression < newStyle.length; subexpression++) {
						matchedString = bestMatch[subexpression + 1];
						output(matchedString, newStyle[subexpression]);
					}
				} else {
					matchedString = bestMatch[0];
					output(matchedString, newStyle);
				}

				if ('next' in pattern) {
					// this was the start of a delimited pattern or a
					// state/environment
					patternStack.push(pattern);
				} else {
					// this was an ordinary pattern
					if ('exit' in pattern) {
						patternStack.pop();
					}
					if ('exitall' in pattern) {
						while (patternStack.getLength() > 0) {
							patternStack.pop();
						}
					}
				}
			}
		}

		// end of the line
		if (currentStyle) {
			builder.endElement();
		}
		currentStyle = undefined;
		if (endOfLineMatch) {
			builder.text(endOfLineMatch[0]);
		}
		pos = startOfNextLine;
	}
}

// //////////////////////////////////////////////////////////////////////////////
// DOM-dependent functions

function sh_getClasses(element) {
	var result = [];
	var htmlClass = element.className;
	if (htmlClass && htmlClass.length > 0) {
		var htmlClasses = htmlClass.split(" ");
		for (var i = 0; i < htmlClasses.length; i++) {
			if (htmlClasses[i].length > 0) {
				result.push(htmlClasses[i]);
			}
		}
	}
	return result;
}

function sh_addClass(element, name) {
	var htmlClasses = sh_getClasses(element);
	for (var i = 0; i < htmlClasses.length; i++) {
		if (name.toLowerCase() === htmlClasses[i].toLowerCase()) {
			return;
		}
	}
	htmlClasses.push(name);
	element.className = htmlClasses.join(" ");
}

/**
 * Extracts the text of an element.
 * 
 * @param element
 *            a DOM
 * 
 * <pre>
 * element
 * @return  the element's text
 * 
 */
function sh_getText(element) {
	// only works in some browsers
	// if (element.nodeType === element.TEXT_NODE ||
	// element.nodeType === element.CDATA_SECTION_NODE) {
	if (Ext.isIE)
		return element.innerText;
	if (element.nodeType === 3 || element.nodeType === 4) {
		return element.data;
	} else if (element.tagName.toUpperCase() === "BR")
		return '\n';
	else if (element.childNodes.length === 1) {
		return sh_getText(element.firstChild);
	} else {
		var result = '';
		for (var i = 0; i < element.childNodes.length; i++) {
			result += sh_getText(element.childNodes.item(i));
		}
		return result;
	}
}

function sh_isEmailAddress(url) {
	if (/^mailto:/.test(url)) {
		return false;
	}
	return url.indexOf('@') !== -1;
}

var sh_builder = {
	init : function(htmlDocument, element) {
		while (element.hasChildNodes()) {
			element.removeChild(element.firstChild);
		}
		this._document = htmlDocument;
		this._element = element;
		this._currentText = null;
		// we use a DocumentFragment because it is faster to build a DOM
		// "offline"
		this._documentFragment = htmlDocument.createDocumentFragment();
		this._currentParent = this._documentFragment;
		// it is faster to clone an existing element than to create from scratch
		this._span = htmlDocument.createElement("span");
		this._a = htmlDocument.createElement("a");
	},
	startElement : function(style) {
		// this._appendText();
		if (this._currentText !== null) {
			this._currentParent.appendChild(this._document
					.createTextNode(this._currentText));
			this._currentText = null;
		}
		var span = this._span.cloneNode(true);
		span.className = style;
		this._currentParent.appendChild(span);
		this._currentParent = span;
	},
	endElement : function() {
		// this._appendText();
		if (this._currentText !== null) {
			if (this._currentParent.className === 'sh_url') {
				var a = this._a.cloneNode(true);
				a.className = 'sh_url';
				var url = this._currentText;
				if (url.length > 0 && url.charAt(0) === '<'
						&& url.charAt(url.length - 1) === '>') {
					url = url.substr(1, url.length - 2);
				}
				if (sh_isEmailAddress(url)) {
					url = 'mailto:' + url;
				}
				a.setAttribute('href', url);
				a.appendChild(this._document.createTextNode(this._currentText));
				this._currentParent.appendChild(a);
			} else if ((typeof Shinji == 'undefined')
					&& this._currentParent.className === 'sh_extpredefined') {
				var a = this._a.cloneNode(true);
				var exp=/(Ext(?:\.(?:air|data|dd|form|grid|layout|menu|state|tree|util))?(?:\.(?:[A-Z][a-z]*)+)?)(?:\.([a-z][a-zA-Z0-9]*))?/;
				var arr =exp.exec(this._currentText);
				var classn, membern;
				classn=arr[1];
				membern=arr[2];
				if(membern) membern='#'+membern;
				a.className = 'sh_url';
				var url = 'output.php?cls=' + classn + (membern ? membern : '');
				a.setAttribute('href', url);
				a.setAttribute('ext:cls', classn);
				a.setAttribute('ext:member', membern);
				a.appendChild(this._document.createTextNode(this._currentText));
				this._currentParent.appendChild(a);
			} else {

				this._currentParent.appendChild(this._document
						.createTextNode(this._currentText));
			}
			this._currentText = null;
		}
		this._currentParent = this._currentParent.parentNode;
	},
	text : function(s) {
		if (this._currentText === null) {
			this._currentText = s;
		} else {
			this._currentText += s;
		}
	},
	/*
	 * _appendText: function() { if (this._currentText !== null) {
	 * this._currentParent.appendChild(this._document.createTextNode(this._currentText));
	 * this._currentText = null; } },
	 */
	close : function() {
		// this._appendText();
		if (this._currentText !== null) {
			this._currentParent.appendChild(this._document
					.createTextNode(this._currentText));
			this._currentText = null;
		}
		this._element.appendChild(this._documentFragment);
	}
};

/**
 * Highlights an element containing source code. Upon completion of this
 * function, the element will have been placed in the "sh_sourceCode" class.
 * 
 * @param HTMLElement element
 * @param language  a language definition object
 * 
 */
function sh_highlightElement(htmlDocument, element) {
	// sh_addClass(element, "sh_sourceCode");
	inputString = sh_getText(element.parentNode);
	sh_builder.init(htmlDocument, element);
	sh_highlightString(inputString, sh_languages['javascript'], sh_builder);
	sh_builder.close();
}
/**
 * Highlights all elements containing source code on the current page. Elements
 * containing source code must be "pre" elements with a "class" attribute of
 * "sh_LANGUAGE", where LANGUAGE is a valid language identifier; e.g., "sh_java"
 * identifies the element as containing "java" language source code.
 */
function sh_highlightHTMLDoc_ByTag(htmlDocument, tag) {
	if (!window.sh_languages) {
		return;
	}
	var nodeList = htmlDocument.getElementsByTagName(tag);
	for (var i = 0; i < nodeList.length; i++) {
		var element = nodeList.item(i);
		sh_highlightElement(document, element);
	}
}
function sh_highlightHTMLDocument(htmlDocument) {
	sh_highlightHTMLDoc_ByTag(htmlDocument, "code");
}

/**
 * The current page is specified via the "document" property of the global
 * object.
 */
function sh_highlightDocument() {
	sh_highlightHTMLDocument(document);
}
