function gsbosEngine()
{
	this.console = fetch_object("gsbosshow");
	this.input = fetch_object("gsbos_message");
	this.typingInterval = -1;
	this.checkInterval = -1;
	this.typingSpeedNormal = 65;
	this.checkSpeed = 12000;
	this.typingLeft = '';
	this.cutoff = '0';
	this.ajax;
	this.bfr = 0;
	this.dummyElements = null;
	this.doHTML = true;
	
	this.init = function(bfrate, speed, rint)
	{
		this.bfr = bfrate;
		this.typingSpeedNormal = speed;
		this.checkSpeed = rint;
		
		this.dummyElements = this.elementList("area", "base", "basefont", "br", "col", "frame", "hr", "img", "input", "isindex", "link", "meta", "param", "embed");
		
		this.showMessages(true);
	}
	
	this.elementList = function()
	{
		var result = new Object();
		for (var i = 0; i < arguments.length; i++)
		{
			result[arguments[i]] = true;
		}
		return result;
	}
	
	this.showMessages = function(clearFirst)
	{
		if (clearFirst)
		{
			gsbos.clearConsole();
		}
		
		gsbos.ajax = new vB_AJAX_Handler(true);
		gsbos.ajax.onreadystatechange(gsbos.receiveData);
		gsbos.ajax.send('gsbos.php', 'do=listMessages&c=' + PHP.urlencode(gsbos.cutoff));
	}
	
	this.receiveData = function()
	{
		var a = gsbos.ajax;
		if (a.handler.readyState == 4 && a.handler.status == 200)
		{
			var response = a.handler.responseText;
			gsbos.cutoff = a.handler.getResponseHeader("GSBOS-Cutoff");
			
			if (response == null) response = '';
			
			if (response.length > 0)
			{
				if (gsbos.checkInterval > 0)
				{
					clearInterval(gsbos.checkInterval);
					gsbos.checkInterval = -1;
				}
				
				gsbos.startTyping(response);
			}
			else
			{
				if (gsbos.checkInterval < 0) gsbos.checkInterval = setInterval(gsbos.showMessages, gsbos.checkSpeed, null);
			}
		}
	}
	
	this.clearConsole = function()
	{
		while (this.console.firstChild) {
			this.console.removeChild(this.console.firstChild);
		}
		this.console.appendChild(document.createTextNode(""));
	}
	
	this.startTyping = function(text)
	{
		if ((this.typingLeft.length > 0) || (this.typingInterval > -1))
		{
			this.typingLeft += text;
		}
		else
		{
			this.typingLeft = text;
			this.typingInterval = setInterval(gsbos.continueTyping, this.typingSpeedNormal, null);
		}
	}
	
	this.continueTyping = function()
	{
		if (this.continueTyping == null) return gsbos.continueTyping();
		
		if (this.typingLeft.length == 0)
		{
			clearInterval(this.typingInterval);
			this.typingInterval = -1;
			this.typingLeft = '';
			
			if (gsbos.checkInterval > 0)
			{
				clearInterval(gsbos.checkInterval);
				gsbos.checkInterval = -1;
			}
			
			this.showMessages(); // Check again after we have nothing left to type, smoothes the rate of incoming messages
		}
		else
		{
			var letter = this.typingLeft.substr(0, 1);
			this.typingLeft = this.typingLeft.substr(1);
			
			if (letter == "\1")
			{
				clearInterval(this.typingInterval);
				this.typingInterval = setInterval(gsbos.continueTyping, parseInt(this.grabParams()), null);
			}
			else if (letter == "\2")
			{
				clearInterval(this.typingInterval);
				this.typingInterval = setInterval(gsbos.continueTyping, this.typingSpeedNormal, null);
			}
			else if (letter == "\3")
			{
				if (this.grabParams().toLowerCase() == "w")
				{
					this.performBackspace(true);
				}
				else
				{
					this.performBackspace();
				}
			}
			else if (letter == "\4")
			{
				while (this.typingLeft.substr(0, 1) != "\2")
				{
					var l = this.typingLeft.substr(0, 1);
					this.typingLeft = this.typingLeft.substr(1);
					
					if (l == "<")
					{
						this.typingLeft = this.processHTML(l + this.typingLeft);
					}
					else if (l == "&")
					{
						this.typingLeft = this.parseSpecialChar(l + this.typingLeft);
					}
					else
					{
						this.appendText(l);
					}
				}
				this.typingLeft = this.typingLeft.substr(1);
			}
			else if (letter == "\6")
			{
				this.appendBarrier(); // Mark the barrier
				
				return this.continueTyping(); // Skip this letter, process again to save delays
			}
			else if (letter == "\7")
			{
				this.clearConsole();
			}
			else if (Math.floor(Math.random()*100+1) <= this.bfr)
			{
				this.appendText(String.fromCharCode(letter.charCodeAt(0) + Math.floor(Math.random()*3+1)));
				this.typingLeft = "\3l\5" + letter + this.typingLeft;
			}
			else if (letter == "<")
			{
				// Do some HTML.
				this.typingLeft = this.processHTML(letter + this.typingLeft);
				
				return this.continueTyping();
			}
			else if (letter == "&")
			{
				this.typingLeft = this.parseSpecialChar(letter + this.typingLeft);
			}
			else
			{
				this.appendText(letter);
			}
		}
	}
	
	this.processHTML = function(input)
	{
		if (input.substr(0, 2) == '</') return this.processHTMLClose(input);
		
		var match = input.match(/^<(\w+)((?:\s+\w+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/);
		var c = this.getLastNode(this.console);
		var attr = new Array();
		var scrollAfter = true;
		
		if ((!match) || (!this.doHTML)) return '&lt;' + input.substr(1);
		
		var tag = match[0];
		var name = match[1];
		var attributes = match[2];
		var closed = !!match[3] || this.dummyElements[name];
		
		var element = document.createElement(name);
		
		attributes.replace(
			/(\w+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g,
			function()
			{
				var name = arguments[1];
				var value = arguments[2] ? arguments[2] :
							arguments[3] ? arguments[3] :
							arguments[4] ? arguments[4] :
							'';
				if (name == "style")
				{
					element.style.cssText = value;
				}
				else
				{
					var attr = document.createAttribute(name);
					attr.value = value;
					element.setAttributeNode(attr);
				}
			}
		);
		
		if (!closed)
		{
			element.appendChild(document.createTextNode(''));
		}
		else
		{
			c.appendChild(document.createTextNode(''));
		}
		
		if (this.console.scrollTop < (this.console.scrollHeight - this.console.clientHeight)) scrollAfter = false;
		c.appendChild(element);
		if (scrollAfter) this.console.scrollTop = this.console.scrollHeight;
		
		return input.substr(match[0].length);
	}
	
	this.processHTMLClose = function(input)
	{
		var match = input.match(/^<\/\w+[^>]*>/);
		var element = null;
		
		if (!match) return input.substr(1);
		
		element = this.getLastNode(this.console);
		element = element.parentNode;
		
		element.appendChild(document.createTextNode(''));
		
		return input.substr(match[0].length);
	}
	
	this.parseSpecialChar = function(input)
	{
		var match = input.match(/^&([#\w]+);/);
		var char = '';
		
		if (!match) return input.substr(1);
		
		switch (match[1].toLowerCase())
		{
			case 'amp':
				char = '&';
				break;
			case 'lt':
				char = '<';
				break;
			case 'gt':
				char = '>';
				break;
			case 'quot':
				char = '"';
				break;
			case '#039':
				char = "'";
				break;
		}
		
		this.appendText(char);
		
		return input.substr(match[0].length);
	}
	
	this.performBackspace = function(asWord)
	{
		var text = this.getLastTextNode(this.console);
		
		if (text == null)
		{
			var del = null;
			
			text = this.getLastNode(this.console, true);
			del = text;
			text = text.parentNode;
			if (text.nodeName == 'dnd') return;
			
			
			text.removeChild(del);
			
			return this.performBackspace();
		}
		
		if (asWord == null) asWord = false;
		
		if (text.data.length > 0)
		{
			text.deleteData(text.data.length - 1, 1);
			
			if (asWord)
			{
				var last = text.data.substr(text.data.length - 1);
				if ((last != "\n") &&
					(last != " "))
				{
					this.performBackspace(true);
				}
			}
		}	
	}
	
	this.getLastTextNode = function(element)
	{
		if (element.lastChild)
		{
			if (element.lastChild.nodeType != 3)
			{
				return this.getLastTextNode(element.lastChild);
			}
			else
			{
				return element.lastChild;
			}
		}
		else
		{
			return null;
		}
	}
	
	this.getLastNode = function(element, includeDummies)
	{
		if (includeDummies == null) includeDummies = false;
		
		if (element.lastChild)
		{
			if ((element.lastChild.nodeType == 1) && (!!this.dummyElements[element.lastChild.nodeName.toLowerCase()] == includeDummies))
			{
				return this.getLastNode(element.lastChild, includeDummies);
			}
			else
			{
				return element;
			}
		}
		else
		{
			return element;
		}
	}
	
	this.grabParams = function()
	{
		var temp = this.typingLeft;
		var len = 0;
		
		while (temp.substr(0, 1) != "\5")
		{
			temp = temp.substr(1);
			len++;
		}
		
		temp = this.typingLeft;
		this.typingLeft = this.typingLeft.substr(len + 1);
		return temp.substr(0, len);
	}
	
	this.sendInput = function()
	{
		if (this.input.value.length > 0)
		{
			this.ajax = new vB_AJAX_Handler(true);
			this.ajax.onreadystatechange(this.receiveData);
			this.ajax.send('gsbos.php', 'do=sendMessage&c=' + PHP.urlencode(this.cutoff) + "&m=" + PHP.urlencode(this.input.value));
		}
		
		this.input.value = '';
	}
	
	this.appendBarrier = function()
	{
		this.console.appendChild(document.createElement('dnd'));
		this.console.appendChild(document.createTextNode(''));
	}
	
	this.appendText = function(t)
	{
		var scrollAfter = true;
		var hasNewline = false;
		var console = this.console;
		var c = this.getLastTextNode(console);
		
		if (console.scrollTop < (console.scrollHeight - console.clientHeight)) scrollAfter = false;
		
		if (c == null)
		{
			console.appendChild(c = document.createTextNode(''));
		}
		
		if (t.substr(t.length - 1) == "\n")
		{
			t = t.substr(0, t.length - 1);
			hasNewline = true;
		}
		
		c.appendData(t);
		
		if (hasNewline)
		{
			console.appendChild(document.createElement("br"));
			console.appendChild(document.createTextNode("\n"));
		}
		
		if (scrollAfter) console.scrollTop = console.scrollHeight;
	}
}