function Wiki()
{
	this.cursor;
	this.editing;
	this.region;
	this.IsEdited = false;
	
	this.diffRegion; // used by the diff operation
	
	this.mostCurrentRegionContents;
	this.IsEditingHistory = false;
	
	this.versionIndex = 0; // current version loaded (0 = current, -x = x backwards)
}

Wiki.ServerSideScriptURL = "wiki.php";

Wiki.setOpacity = function(obj, opacity) 
{
  opacity = (opacity == 100)?99.999:opacity;
  
  // IE/Win
  obj.style.filter = "alpha(opacity:"+opacity+")";
  
  // Safari<1.2, Konqueror
  obj.style.KHTMLOpacity = opacity/100;
  
  // Older Mozilla and Firefox
  obj.style.MozOpacity = opacity/100;
  
  // Safari 1.2, newer Firefox and Mozilla, CSS3
  obj.style.opacity = opacity/100;
}

Wiki.StartWait = function()
{
	if( Wiki.waitIndex ) 
	{
		Wiki.waitIndex++;
	}
	else
	{
		Wiki.waitIndex = 1;
	}
	
	document.getElementsByTagName("body")[0].style.cursor = "wait";
	
	if( Wiki.GetInstance().region ) 
	{
		setTimeout( "if( Wiki.waitIndex > 0 ) Wiki.setOpacity( Wiki.GetInstance().region, 60 );", 100 );
	}
}

Wiki.StopWait = function()
{
	Wiki.waitIndex--;
	
	if( Wiki.waitIndex <= 0 ) 
	{
		document.getElementsByTagName("body")[0].style.cursor = "auto";
		Wiki.waitIndex = 0;
		
		if( Wiki.GetInstance().region ) Wiki.setOpacity( Wiki.GetInstance().region, 100 );
	}
}

Wiki.prototype.Reset = function()
{
	if( this.versionIndex < 0 && ! this.IsEditingHistory )
	{
		this.versionIndex = 0;
		this.region.onmousedown = Wiki.ClickRegion;
		this.region.onmouseup = Wiki.DragSelection;
		document.onkeydown = null;
		Wiki.GetCurrentVersion( this.region );
	}

	this.IsEdited = false;
	this.IsEditingHistory = false;
	this.versionIndex = 0;
	this.cursor = null;
	this.editing = null;
	this.region = null;
}

Wiki.prototype.RemoveCoordinateHolders = function()
{
	if( ! this.region.AddedHolders ) return;
		
	var holders = this.region.getElementsByTagName("span");
	var numHolders = holders.length;
	for( var i = numHolders; i >= 0 ; i-- )
	{
		if( holders[i] && holders[i].className == "CoordinateHolder" )
		{
			holders[i].parentNode.removeChild( holders[i] );
		}
	}
	
	this.region.AddedHolders = false;
}

Wiki.GoToRegion = function( regionId )
{
	if( ! /^http:\/\//i.test( document.location + "" ) ) return;
	
	var req = new XMLHttpRequest();
	req.open("POST", Wiki.ServerSideScriptURL, true);
	req.onreadystatechange = function() 
	{ 
		Wiki.GoToRegion_Handler( req, regionId ); 
	};
	req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	req.send( 
		"action=findRegion"
		+ "&region=" + escape( regionId )
	);
	
	return false;
}

Wiki.GoToRegion_Handler = function( req, regionId )
{
	if( req.readyState == 4 )
	{
		var serverResponse = req.responseText;
		
		if( /^<response>http:\/\/.*<\/response>$/i.test( serverResponse ) )
		{
			document.location = serverResponse.match( /(http:\/\/.*)/i )[0];
			return;
		}
		else
		{
			var popupWindow = window.open( "", "WikiPopup", "height=200,width=150" );
			
			if( serverResponse.length < 3 )
			{
				serverResponse = "Welcome to your brand new wiki page!  Click here to start editing :-)";
			}
			
			popupWindow.document.write( "<div id='" + regionId + "'>" + serverResponse + "</div>" );
			popupWindow.document.close();
			
			if( popupWindow.focus ) popupWindow.focus();
		}
	}
}

Wiki.SaveChanges = function()
{
	var myWiki = Wiki.GetInstance();
	
	if( ! myWiki.region ) return;
	
	myWiki.RemoveCoordinateHolders();
	
	if( ! /^http:\/\//i.test( document.location + "" ) ) return;
	
	Wiki.StartWait();

	var req = new XMLHttpRequest();
	req.open("POST", Wiki.ServerSideScriptURL, true);
	req.onreadystatechange = function() 
	{ 
		Wiki.SaveChanges_Handler( req ); 
	};
	req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	
	req.send( 
		"action=put"
		+ "&region=" + escape( myWiki.region.id )
		+ "&page=" + escape( document.location )
		+ "&html=" + escape( myWiki.region.innerHTML )
	);

}

Wiki.SaveChanges_Handler = function( req )
{
	if( req.readyState == 4 )
	{
		Wiki.StopWait();
		var serverResponse = req.responseText;
		
	}
}

Wiki.GetCurrentVersion = function( region )
{
	Wiki.StartWait();
	var myWiki = Wiki.GetInstance();
	
	if( region )
	{
		myWiki.GetVersion( 0, function( versionSource ) 
			{ 
				Wiki.GetCurrentVersion_Handler( region, versionSource );
			},
			region
		);
	}
	else
	{
		myWiki.GetVersion( 0, this.GetCurrentVersion_Handler );
	}
}

Wiki.GetCurrentVersion_Handler = function( region, versionSource )
{
	Wiki.StopWait();
	if( versionSource.length > 1 )
	{
		region.innerHTML = versionSource;
	}
	else if( ( region.innerHTML ).length < 3 )
	{
		region.innerHTML = "Here's your new wiki!  Click here to start editing :-)";
		
	}
	
	Wiki.InsertCoordinateHolders( region );
}

Wiki.prototype.GetVersion = function( version, sendVersionTo, region )
{	
	if( ! /^http:\/\//i.test( document.location + "" ) ) return;
	if( ! region ) region = Wiki.GetInstance().region;
	
	Wiki.StartWait();	

	var req = new XMLHttpRequest();
	req.open("POST", Wiki.ServerSideScriptURL, true);
	req.onreadystatechange = function() 
	{ 
		Wiki.GetInstance().GetVersion_Handler( req, version, sendVersionTo ); 
	};
	req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	req.send( 
		"action=get"
		+ "&region=" + escape( region.id )
		+ "&page=" + escape( document.location )
		+ "&version=" + escape( version )
	);
	
}

Wiki.prototype.GetVersion_Handler = function( req, version, sendVersionTo )
{
	if( req.readyState == 4 )
	{
		Wiki.StopWait();
		if( this.versionIndex == 0 )
		{
			this.mostCurrentRegionContents = req.responseText;
		}
	
		this.versionIndex = version;
		if( sendVersionTo ) sendVersionTo( req.responseText );
	}
}

Wiki.DiffRegion = function( version )
{
	Wiki.StartWait();
	var myWiki = Wiki.GetInstance();

	if( ! myWiki.IsEditingHistory )
	{
		if( ! version )
		{
			version = myWiki.versionIndex - 1;
		}

		myWiki.GetVersion( version, Wiki.DiffRegion_Handler );
	}
	else
	{
		myWiki.IsEditingHistory = false;
		myWiki.versionIndex = 0;
		
		myWiki.RemoveCoordinateHolders();
		
		myWiki.editing.data = myWiki.editing.data.substring( 0, myWiki.cursor ) 
			+ myWiki.editing.data.substring( myWiki.cursor+1 );
			
		myWiki.diffRegion = myWiki.region.innerHTML;
		
		Wiki.DiffRegion_Handler( myWiki.mostCurrentRegionContents, false );
	}
}

Wiki.DiffRegion_Handler = function( historicalRegion, SaveCurrentVersionSettings )
{
	Wiki.StopWait();
	if( SaveCurrentVersionSettings == undefined ) SaveCurrentVersionSettings = true;

	var myWiki = Wiki.GetInstance();
	
	if( myWiki.editing )
	{
		myWiki.editing.data = myWiki.editing.data.substring( 0, myWiki.cursor ) 
			+ myWiki.editing.data.substring( myWiki.cursor+1 );
		myWiki.editing = null;
	}

	if( SaveCurrentVersionSettings && myWiki.versionIndex == -1 ) // if we just left the current version
	{
		var oldContents = myWiki.region.innerHTML;
		
		myWiki.RemoveCoordinateHolders();
		myWiki.diffRegion = myWiki.region.innerHTML;
		myWiki.mostCurrentRegionContents = myWiki.region.innerHTML + "";
		//myWiki.region.innerHTML
		
	}
	try
	{
		myWiki.region.innerHTML = diffString( 
			historicalRegion,
			myWiki.diffRegion	
		);	
	}
	catch( e )
	{
		throw new Error( "diffString function threw an exception.  Stopping diff execution.");
		return;
	}
	
	var currentNewContents = myWiki.diffRegion + "";
	myWiki.diffRegion = historicalRegion;
	
	if( SaveCurrentVersionSettings && myWiki.versionIndex == -1 ) // if we just left the current version
	{
		myWiki.region.onmousedown = function( event )
		{
			Wiki.GetInstance().versionIndex = 0;
			this.innerHTML = oldContents;
			this.onmousedown = Wiki.ClickRegion;
			this.onmouseup = Wiki.DragSelection;
		}
	}
	else
	{	
		myWiki.region.onmousedown = function( event )
		{
			if( confirm( "You are editing an old version of this region.\nYour saved changes will overwrite newer versions.\nAre you sure you want to proceed?" ) )
			{
				Wiki.GetInstance().IsEditingHistory = true;
				this.innerHTML = currentNewContents;
				this.onmousedown = Wiki.ClickRegion;
				this.onmouseup = Wiki.DragSelection;	
			}			
		}	
	}
	
	document.onkeydown = function( event )
	{
		if( ! event ) event = window.event;

		if( event.keyCode ) keyCode = event.keyCode;
		else if( event.which ) keyCode = event.which;
		
		if( keyCode == 113 )
		{
			Wiki.DiffRegion( myWiki.versionIndex - 1 );
		}
		else
		{
			Wiki.GetInstance().versionIndex = 0;
			Wiki.GetInstance().region.onmousedown = Wiki.ClickRegion;
			Wiki.GetInstance().region.onmouseup = Wiki.DragSelection;
			document.onkeydown = null;
			Wiki.GetCurrentVersion( Wiki.GetInstance().region );
			alert( "You have been returned to the most recent version." );
		}
	}
}

Wiki.findPosX = function(obj)
{
	var curleft = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curleft += obj.offsetLeft
			obj = obj.offsetParent;
		}
	}
	else if (obj.x)
		curleft += obj.x;
	return curleft;
}

Wiki.findPosY = function(obj)
{
	var curtop = 0;
	if (obj.offsetParent)
	{
		while (obj.offsetParent)
		{
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	}
	else if (obj.y)
		curtop += obj.y;
		
	return curtop;
}

Wiki.windowState = function() 
{
	// IE
	if( document.all )
	{
		this.width = document.body.offsetWidth;
		this.height = document.body.offsetHeight;
		this.scrollX = document.body.scrollLeft;
		this.scrollY = document.body.scrollTop;
	}
	// Mozilla
	else
	{	
		this.width = window.innerWidth;
		this.height = window.innerHeight;
		this.scrollX = window.pageXOffset;
		this.scrollY = window.pageYOffset;
	}
}




Wiki.GetInstance = function()
{
	if( ! Wiki.__instance )
	{
		Wiki.__instance = new Wiki();
	}
	
	return Wiki.__instance;
}

Wiki.BeginEdit = function( editing, at )
{
	var myWiki = Wiki.GetInstance();
	
	myWiki.IsEdited = false;
	myWiki.editing = editing;
	
	if( at && at < myWiki.editing.data.length )
	{
		// DO NOTHING
	}
	else
	{
		at = myWiki.editing.data.length;
	}
	
	myWiki.editing.data = myWiki.editing.data.substring( 0, at )
		+ "|" + myWiki.editing.data.substring( at );
	myWiki.cursor = at;
	
	document.onkeydown = function( event ) { return myWiki.KeyHandler(event); };
	document.onkeypress = function( event ) { return false; };
}

Wiki.prototype.KeyHandler = function(event) 
{ 
				if( ! event ) event = window.event;
			
				if( event.keyCode ) keyCode = event.keyCode;
				else if( event.which ) keyCode = event.which;
				
				this.IsEdited = true;
				
				switch( keyCode)
				{
					case 8: // backspace
						if( this.cursor <= 0 )
						{
							this.editing.data = this.editing.data.substring( 1 );
							
							if( this.editing.data.length <= 0 )
							{
								if( this.editing.previousSibling
									&& this.editing.previousSibling.tagName
									&& this.editing.previousSibling.tagName == "BR"
								)
								{
									this.editing.parentNode.removeChild(
										this.editing.previousSibling );
								}
								else
								{
							
								try
								{
									var lastCursorRegion = this.editing;
									this.MoveCursorTo( this.editing, "left" );
									lastCursorRegion.parentNode.removeChild( lastCursorRegion );
								}
								catch( e )
								{
									this.editing.data = "|" + this.editing.data;
									break;
								}
								
								
								// TODO: Keep moving up the DOM delete empty elements
								
								}
							}
							else
							{
								try
								{
									this.MoveCursorTo( this.editing, "left" );
								}
								catch( e )
								{
									this.editing.data = "|" + this.editing.data;
									break;
								}
							}
						}

						this.editing.data = this.editing.data.substring( 0, this.cursor-1 )
							+ "|"
							+ this.editing.data.substring( this.cursor + 1 );
						this.cursor--;
						
						break;
					case 32: // spacebar
						this.AddCharacterInput( this.editing, " " );
						
						this.SplitEditingNode();
						this.cursor = 0;
						
						var coordinateHolder = document.createElement( "span" );
						coordinateHolder.className = "CoordinateHolder";
						coordinateHolder.style.position = "relative";
						
						this.editing.parentNode.insertBefore( 
							coordinateHolder, this.editing );
						
						break;
					case 46: // delete
						if( this.cursor >= this.editing.data.length-1 )
						{	
							if( this.editing.nextSibling
								&& this.editing.nextSibling.tagName
								&& this.editing.nextSibling.tagName == "BR"
							)
							{
								this.editing.parentNode.removeChild(
									this.editing.nextSibling );
							}
							else
							{
						
								var oldEditing = ( this.editing );
								oldEditing.data = this.editing.data.substring( 0, 
									oldEditing.data.length - 1 );

								try
								{
									this.MoveCursorTo( this.editing, "right" );
								}
								catch( e )
								{
									oldEditing.data += "|";
									break;
								}
								this.editing.data = this.editing.data.substring( 2 );
							
								try
								{
									this.MoveCursorTo( this.editing, "left" );
								}
								catch( e )
								{
									this.editing.data = "|" + this.editing.data;
									this.cursor = 0;
								}
							
							}
						}
						else
						{
							this.editing.data = this.editing.data.substring( 0, 
								this.cursor+1 ) +
								this.editing.data.substring( this.cursor+2 ) ;
						}
						break;
					case 190: // . >
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, ">" );
						}
						else
						{
							this.AddCharacterInput( this.editing, "." );
						}
						break;
					case 188: // , <
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "<" );
						}
						else
						{
							this.AddCharacterInput( this.editing, "," );
						}
						break;
						
					case 222: // ' "
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "\"" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "'" );
						}
						break;
					case 48: // 0 )
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, ")" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "0" );
						}
						break;
					case 49: // 1
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "!" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "1" );
						}
						break;
					case 50: // 2
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "@" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "2" );
						}
						break;
					case 51: // 3
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "#" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "3" );
						}
						break;
					case 52: // 4
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "$" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "4" );
						}
						break;
					case 53: // 5
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "%" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "5" );
						}
						break;
					case 54: // 6
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "^" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "6" );
						}
						break;
					case 55: // 7
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "&" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "7" );
						}
						break;
					case 56: // 8
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "*" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "8" );
						}
						break;
					case 57: // 9
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "(" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "9" );
						}
						break;
					case 59: // ; :
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, ":" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, ";" );
						}
						break;
					case 61: // = +
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "+" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "=" );
						}
						break;
					case 192: // ` ~
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "~" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "`" );
						}
						break;
					case 219: // [ {
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "{" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "[" );
						}
						break;
					case 220: // = +
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "|" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "\\" );
						}
						break;
					case 221: // ] }
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "}" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "]" );
						}
						break;
					case 109: // - _
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "_" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "-" );
						}
						break;
					case 191: // ? /
						if( event.shiftKey )
						{
							this.AddCharacterInput( this.editing, "?" );
						}
						else
						{	
							this.AddCharacterInput( this.editing, "/" );
						}
						break;
					case 38: // up arrow
						var holders = this.region.getElementsByTagName( "span" );
						var upwardsHolder;
						
						var previous;
						if( ! ( previous = this.editing.previousSibling ) )
						{
							previous = this.editing.parentNode.previousSibling;
						}

						for( var i = 0; i < holders.length; i++ )
						{
							if( holders[i].className == "CoordinateHolder" )
							{
								if( Wiki.findPosY( holders[i] )
									< Wiki.findPosY( previous ) )
								{
									if( ! upwardsHolder || Wiki.findPosY( holders[i] ) 
										!= Wiki.findPosY( upwardsHolder ) )
									{
										upwardsHolder = holders[i];
									}
									else
									{
										if( Wiki.findPosX( holders[i] ) 
											<= Wiki.findPosX( previous ) )
										{
											upwardsHolder = holders[i];
										}
									}
								}
								else
								{
									break;
								}
							}
						}
						
						if( upwardsHolder )
						{
							this.editing.data = this.editing.data.substring( 0, this.cursor ) 
								+ this.editing.data.substring( this.cursor + 1);
							Wiki.BeginEdit( upwardsHolder.nextSibling );
						}
						break
					case 40: // down arrow
						var holders = this.region.getElementsByTagName( "span" );
						var downwardsHolder;
						
						var previous;
						if( ! ( previous = this.editing.previousSibling ) )
						{
							previous = this.editing.parentNode.previousSibling;
						}						

						for( var i = holders.length-1; i >= 0; i-- )
						{
							if( holders[i].className == "CoordinateHolder" )
							{
								if( Wiki.findPosY( holders[i] )
									> Wiki.findPosY( previous ) )
								{
									if( ! downwardsHolder || Wiki.findPosY( holders[i] ) 
										!= Wiki.findPosY( downwardsHolder ) )
									{
										downwardsHolder = holders[i];
									}
									else
									{
										if( Wiki.findPosX( holders[i] ) 
											<= Wiki.findPosX( previous ) )
										{
											downwardsHolder = holders[i];
										}
									}
								}
								else
								{
									break;
								}
							}
						}
						
						if( downwardsHolder )
						{
							this.editing.data = this.editing.data.substring( 0, this.cursor ) 
								+ this.editing.data.substring( this.cursor + 1);
							Wiki.BeginEdit( downwardsHolder.nextSibling );
						}
						break;
					
					case 37: // left arrow
						if( this.cursor > 1 )
						{
							this.editing.data = this.editing.data.substring( 0, this.cursor-1 )
								+ "|"
								+ this.editing.data.charAt( this.cursor -1 ) 
								+ this.editing.data.substring( this.cursor + 1 );
							this.cursor--;							
						}
						else
						{
							this.editing.data = this.editing.data.substring( 0, this.cursor )
								+ this.editing.data.substring( this.cursor+1 );

							try
							{
								this.MoveCursorTo( this.editing, "left" );
							}
							catch( e )
							{
								this.cursor = 0;
								this.editing.data = "|" + this.editing.data;
							}
						}
						break;
					case 39: // right arrow
						if( this.cursor < this.editing.data.length-1  )
						{
							this.editing.data = this.editing.data.substring( 0, this.cursor )
								+ this.editing.data.charAt( this.cursor + 1 ) 
								+ "|"
								+ this.editing.data.substring( this.cursor + 2);
							this.cursor++;	
						}
						else
						{
							this.editing.data = this.editing.data.substring( 0, this.editing.data.length-1 );
							try
							{
								this.MoveCursorTo( this.editing, "right" );
							}
							catch( e )
							{
								this.cursor = this.editing.data.length;
								this.editing.data += "|";
							}
							
						}
						break;
					case 13: // enter
						this.SplitEditingNode();
						
						if( this.editing.previousSibling.data.length <= 0 )
						{
							this.editing.previousSibling.data = "\n";
						}
						
						var br = document.createElement("br");
						
						this.editing.parentNode.insertBefore(
							br, this.editing );
						this.cursor = 0;
						
						break;
						
					case 113: // F2
						Wiki.DiffRegion( );
						break;
						
					case 76: // "l"
						if( event.ctrlKey )
						{
							var url;
							var anchorName;
							
							if( 
								( url = prompt( "URL (beginning with http://) or id of wiki page\n(if it doesn't exist, a new page will be created):", "http://" ) )
								&& ( anchorName = prompt( "Description to appear on page:", "" ) )
							)
							{
								var anchor = document.createElement("a");
								anchor.appendChild( document.createTextNode( anchorName ) );
								
								if( /^http:\/\//i.test( url ) )
								{
									anchor.href = url;
								}
								else
								{
									anchor.href = Wiki.ServerSideScriptURL + "?redirect=" + url;
									//anchor.WikiRegion = url;
									//anchor.onmousedown = "return Wiki.GoToRegion( this.WikiRegion );"
								}
							
								this.SplitEditingNode();
						
								this.editing.parentNode.insertBefore(
									anchor, this.editing );
								this.cursor = 0;
							}
						}
						else
						{
							this.AddCharacterInputFromKeyCode( event, keyCode );
						}
				
						break;						
					case 66: // "b"
						if( event.ctrlKey )
						{	
							var isBolded = false;
						
							var parents = this.editing.parentNode;
							while( parents && parents != this.region )
							{
								if( parents.tagName == "STRONG" )
								{
									if( this.DepthFirstSearchForData( parents, this.editing ) 
									|| (
										this.cursor < this.editing.data.length-1  
										&& this.cursor > 0
									   )			
									)
									// If the cursor is not at the rightmost point of the EM element
									{
										// Remove strong for sub elements
										while( parents.childNodes.length > 0 )
										{
											parents.parentNode.insertBefore(
												parents.firstChild, parents );
										}
									}
									else if( this.cursor >= this.editing.data.length-1 )
									{
										// Stop bolding at this point
										this.editing.data = 
											this.editing.data.substring( 0,
												this.cursor );
											
										parents.parentNode.insertBefore(
											document.createTextNode( "*" ),
											parents.nextSibling );
											
										this.MoveCursorTo( this.editing, "right" );
										
										this.editing.data = "|";
										this.cursor--;
									}
									else if( this.cursor <= 0 )
									{
										// Stop bolding at this point
										this.editing.data = 
											this.editing.data.substring( 1 );
											
										parents.parentNode.insertBefore(
											document.createTextNode( "*" ),
											parents );
											
										this.MoveCursorTo( this.editing, "left" );
										
										this.editing.data = "|";
										this.cursor--;
									}
									isBolded = true;
								}
								parents = parents.parentNode;
							}
							
							if( isBolded ) break;
							
							this.SplitEditingNode();
							
							var strong = document.createElement("strong");
							strong.appendChild( document.createTextNode( " " ) );
							
							this.editing.parentNode.insertBefore(
								strong, this.editing );

							this.editing.data = this.editing.data.substring( 1 );
							
							this.MoveCursorTo( this.editing, "left" );
							
							this.editing.data = this.editing.data.substring( 1 );
							this.cursor--;	
							break;
						}
						else
						{
							this.AddCharacterInputFromKeyCode( event, keyCode );
						}
						
						break;	
					case 73: // "i"
						if( event.ctrlKey )
						{	
							var isItalics = false;	
						
							var parents = this.editing.parentNode;
							while( parents && parents != this.region )
							{
								if( parents.tagName == "EM" )
								{
									if( this.DepthFirstSearchForData( parents, this.editing ) 
									|| (
										this.cursor < this.editing.data.length-1  
										&& this.cursor > 0
									   )			
									)
									// If the cursor is not at the rightmost point of the EM element
									{
										// Remove italics for sub elements
										while( parents.childNodes.length > 0 )
										{
											parents.parentNode.insertBefore(
												parents.firstChild, parents );
										}
									}
									else if( this.cursor >= this.editing.data.length-1 )
									{
										// Stop italicizing at this point
										this.editing.data = 
											this.editing.data.substring( 0,
												this.cursor );
											
										parents.parentNode.insertBefore(
											document.createTextNode( "*" ),
											parents.nextSibling );
											
										this.MoveCursorTo( this.editing, "right" );
										
										this.editing.data = "|";
										this.cursor--;
									}
									else if( this.cursor <= 0 )
									{
										// Stop italicizing at this point
										this.editing.data = 
											this.editing.data.substring( 1 );
											
										parents.parentNode.insertBefore(
											document.createTextNode( "*" ),
											parents );
											
										this.MoveCursorTo( this.editing, "left" );
										
										this.editing.data = "|";
										this.cursor--;
									}
									isItalics = true;
								}
								parents = parents.parentNode;
							}
							if( isItalics ) break;

							this.SplitEditingNode();
							
							var em = document.createElement("em");
							em.appendChild( document.createTextNode( " " ) );
							
							this.editing.parentNode.insertBefore(
								em, this.editing );

							this.editing.data = this.editing.data.substring( 1 );
							
							this.MoveCursorTo( this.editing, "left" );
							
							this.editing.data = this.editing.data.substring( 1 );
							this.cursor--;	
							break;
						}
						else
						{
							this.AddCharacterInputFromKeyCode( event, keyCode );
						}
						
						break;	
					case 83: // "s"
						if( event.ctrlKey )
						{
							if( this.region 
								&& this.IsEdited
								&& this.region != this 
								&& confirm( "Do you want to save your changes?" ) )
							{
								this.editing.data = this.editing.data.substring( 
									0, this.cursor ) 
									+ this.editing.data.substring( this.cursor + 1);
								Wiki.SaveChanges();
								
							}
						}
						else
						{
							this.AddCharacterInputFromKeyCode( event, keyCode );
						}
						break;						
					default:
						if( /^[A-Z0-9]$/i.test( String.fromCharCode( keyCode ) ) )
						{
							this.AddCharacterInputFromKeyCode( event, keyCode );
						}
						else
						{
							//alert( keyCode + " = " + String.fromCharCode( keyCode ) );
						}
						break;
						
				}
				
			//alert( keyCode + " = " + String.fromCharCode( keyCode ) );
				
			//event.keyCode = 0;
			event.cancelBubble = true;
			event.returnValue = false;
			if( event.stopPropagation ) event.stopPropagation();
			return false;
}

Wiki.prototype.AddCharacterInputFromKeyCode = function( event, keyCode)
{
	if( event.shiftKey )
							{
								this.AddCharacterInput( this.editing, String.fromCharCode( keyCode ).toUpperCase() );
							}
							else
							{
								this.AddCharacterInput( this.editing, String.fromCharCode( keyCode ).toLowerCase() );
							}
}


Wiki.prototype.SplitEditingNode = function( editing, at )
{
	if( ! editing )
	{
		editing = this.editing;
	}
	
	if( ! at )
	{
		at = this.cursor;
	}
	
	editing.parentNode.insertBefore(
		document.createTextNode( editing.data.substring( 0, at ) ), editing );
	editing.data = editing.data.substring( at );
}

Wiki.prototype.MoveCursorTo = function( from, direction )
{
	// We've reached the upmost limit that we can edit
	if( from.parentNode == this.region.parentNode )
	{
		throw new Error( "MoveCursorTo: Reached region boundary before being able to move cursor" );
		return;		
	}

	if( direction == "left" )
	{
		foundNode = this.ReverseDepthFirstSearchForData( from.parentNode, from )
	}
	else
	{
		foundNode = this.DepthFirstSearchForData( from.parentNode, from )
	}
	
	if( foundNode )
	{
		this.editing = foundNode;
		
		if( direction == "left" )
		{
			this.editing.data += "|";
			
			this.cursor = this.editing.data.length-1;
		}
		else
		{
			this.editing.data = this.editing.data.charAt(0) + "|" + this.editing.data.substring( 1 );
			this.cursor = 1;
		}
	}
	else
	{
		if( from.parentNode )
		{
			this.MoveCursorTo( from.parentNode, direction );
		}
		else
		{
			throw new Error( "MoveCursorTo: Could not find a parentNode to continue recursion." );
		}
	}

}

Wiki.prototype.ReverseDepthFirstSearchForData = function( root, outOfLimits )
{
	var i = root.childNodes.length-1;
	if( root == outOfLimits.parentNode )
	{
		while ( root.childNodes[i] != outOfLimits )
		{
			i--;
		}
		i--; // move past the outOfLimits
	}

	for( ; i >= 0; i-- )
	{
		if( root.childNodes[i].data )
		{
			return root.childNodes[i];
		}
		else
		{
			var foundNode = this.ReverseDepthFirstSearchForData( root.childNodes[i], outOfLimits );
			if( foundNode )
			{
				return foundNode;
			}
		}
	}
	return;
}

Wiki.prototype.DepthFirstSearchForData = function( root, outOfLimits )
{
	var i = 0;
	if( root == outOfLimits.parentNode )
	{
		while ( root.childNodes[i] != outOfLimits )
		{
			i++;
		}
		i++; // move past the outOfLimits
	}

	for( ; i < root.childNodes.length; i++ )
	{
		if( root.childNodes[i].data )
		{
			return root.childNodes[i];
		}
		else
		{
			var foundNode = this.DepthFirstSearchForData( root.childNodes[i], outOfLimits );
			if( foundNode )
			{
				return foundNode;
			}
		}
	}
	return;
}

Wiki.prototype.AddCharacterInput = function( to, character )
{
	if( ! to.data ) 
	{
		to.appendChild( document.createTextNode( character + "|" ) );
		this.cursor = character.length;
	}
	else 
	{
		to.data = (to.data).substring( 0, this.cursor ) + character + (to.data).substring( this.cursor );
		this.cursor += character.length;
	}
}

Wiki.ActivateElements = function()
{
	var elements = document.getElementsByTagName("*");
	for( var i = 0; i < elements.length; i++ )
	{
		if( elements[i].className && elements[i].className == "Wiki" )
		{
		
		if( ! elements[i].id )
		{
			//throw new Error( "The wiki region does not have an id. Changes will not be sent to the server." );
			elements[i].id = ( elements[i].innerHTML + "" ).substring( 0, 100 );
		}
		
		Wiki.GetCurrentVersion( elements[i] );
				
		elements[i].onmousedown = Wiki.ClickRegion;
		elements[i].onmouseup = Wiki.DragSelection;
		
		}
	}
}

Wiki.DragSelection = function( event )
{
	return; // DISABLE THIS FUNCTION FOR NOW 

	if( ! event ) event = window.event;
	
	var selectedText;
	if( navigator.userAgent.indexOf('Safari') >=0)
	{
		selectedText = getSelection();
	}
	else
	{
		selectedText = document.selection?document.selection.createRange().text:document.getSelection();
	}
	
	if( selectedText.length <= 0 ) return;
	
	var myWiki = Wiki.GetInstance();
	//alert( Wiki.findPosX( myWiki.editing.previousSibling ) + " vs " + event.clientX );
	
	//alert( ( myWiki.region.innerHTML ).replace( /<[^>]*>/gi, "" ).replace( new RegExp(selectedText, "g"), "*******" ) );
	
	
}

Wiki.prototype.RemoveCursor = function()
{
	this.editing.data = this.editing.data.substring( 0, this.cursor ) 
		+ this.editing.data.substring( this.cursor + 1);
	this.editing = null;
	this.cursor = null;
}


Wiki.ClickRegion = function( event ) 
{
			var myWiki = Wiki.GetInstance();
			if( myWiki.editing )
			{
				myWiki.RemoveCursor();
			}
		
			if( myWiki.region 
				&& myWiki.IsEdited
				&& myWiki.region != this 
				&& confirm( "Do you want to save your changes?" ) )
			{
			
				Wiki.SaveChanges();
			}
			
			if( myWiki.region != this )
			{
				myWiki.Reset();
			}
		
			myWiki.region = this;
			
			if( ! event ) event = window.event;
			
			var windowNow = new Wiki.windowState();
			
			/*
			var  dot = document.createElement( "DIV" );
			dot.style.background = "red";
			dot.style.width = "5px";
			dot.style.height = "5px";
			dot.style.position = "absolute";
			dot.style.top = event.clientY + windowNow.scrollY;
			dot.style.left = event.clientX + windowNow.scrollX;
			document.getElementsByTagName("body")[0].appendChild( dot );			
			*/
			Wiki.InsertCoordinateHolders( myWiki.region );
			
			var closestHolder = myWiki.FindClosestHolder( 
				event.clientX + windowNow.scrollX, event.clientY + windowNow.scrollY );
				
			
			
			//alert( (event.clientY + windowNow.scrollY) + " vs " + Wiki.findPosY( closestHolder.leftOfHolder.nextSibling ) );
			
			Wiki.BeginEdit( closestHolder.leftOfHolder, closestHolder.wordStartPoint );
}

Wiki.prototype.FindClosestHolder = function( clientX, clientY )
{
			var holders = this.region.getElementsByTagName("span");
			var closestHolder;
			var lastClosestHolder;
			var xToRight;
			var yBelow;

			for( var j = 1; j < holders.length; j++ )
			{
				if( holders[j].className == "CoordinateHolder" )
				{
					xToRight = Wiki.findPosX( holders[j] ) - clientX;
					yBelow = Math.abs( Wiki.findPosY( holders[j] ) - clientY );
					
					if( ! closestHolder  )
					{
						lastClosestHolder =  closestHolder;
						closestHolder = holders[j];
					}
					else if( closestHolder 
						&& yBelow < Math.abs( Wiki.findPosY( closestHolder ) - clientY ) )
					{
						lastClosestHolder =  closestHolder;
						closestHolder = holders[j];
					}
					else if( closestHolder
						//&& ( holders[j].innerHTML = "[" + Wiki.findPosY( holders[j] ) +"vs"+  Wiki.findPosY( closestHolder )  +"]" )
						&& yBelow == Math.abs( Wiki.findPosY( closestHolder ) - clientY )
						&& 
						( ( 	
							xToRight >= 0
							&& xToRight < Wiki.findPosX( closestHolder ) - clientX 
						)
						||
						(
							xToRight > ( Wiki.findPosX( closestHolder ) - clientX )
							&& ( Wiki.findPosX( closestHolder ) - clientX ) < 0	
						) )
					)
					{
						lastClosestHolder =  closestHolder;
						closestHolder = holders[j];
					}
					
					
				}
				
			}
			
			//if( closestHolder ) closestHolder.innerHTML += "*";
			/*
			var  dotty = document.createElement( "DIV" );
			dotty.style.background = "green";
			dotty.style.width = "5px";
			dotty.style.height = "5px";
			dotty.style.position = "absolute";
			dotty.style.top = Wiki.findPosY( closestHolder );
			dotty.style.left =  Wiki.findPosX( closestHolder  );
			document.getElementsByTagName("body")[0].appendChild( dotty );
			*/
			if( ! closestHolder )
			{
				closestHolder = holders[0];
			}


			
			var leftOfHolder = this.ReverseDepthFirstSearchForData( closestHolder.parentNode, closestHolder );
			
			
			
			if( lastClosestHolder 
				&& Wiki.findPosY( closestHolder ) == Wiki.findPosY( lastClosestHolder )
			)
			{
				var xOfLastClosestHolder = Wiki.findPosX( lastClosestHolder );
			}
			else
			{
				var br = document.createElement( "br" );
				
				var coordinateHolder = document.createElement( "span" );
				coordinateHolder.className = "CoordinateHolder";
				coordinateHolder.style.position = "relative";
			
				lastClosestHolder = leftOfHolder.parentNode.insertBefore( 
					coordinateHolder, leftOfHolder );
				coordinateHolder.parentNode.insertBefore( 
					br, coordinateHolder );

				var xOfLastClosestHolder = Wiki.findPosX( lastClosestHolder );
				
				br.parentNode.removeChild( br );
			}
			
				var wordWidth = Wiki.findPosX( closestHolder ) - xOfLastClosestHolder;
				var clickLocationFromLast = clientX - xOfLastClosestHolder;
				var percentAlongWord = clickLocationFromLast / wordWidth;
				
				var wordLength = leftOfHolder.data.length;
				
				var wordStartPoint = ( Math.floor( wordLength * percentAlongWord ) );
				
	return { leftOfHolder:leftOfHolder, wordStartPoint:wordStartPoint };
}


Wiki.InsertCoordinateHolders = function( region )
{
	if( ! region.AddedHolders )
	{
		Wiki.HighlightSpaces( region );
		region.innerHTML = ( region.innerHTML ).replace( /%%%/g, "<span class='CoordinateHolder' style='position: relative;'></span> " );
		
		region.innerHTML = "<span class='CoordinateHolder' style='position: relative;'></span>" 
			+ region.innerHTML + "<span class='CoordinateHolder' style='position: relative;'></span>";
		
		region.AddedHolders = true;
	}
}

Wiki.HighlightSpaces = function( node )
{
	for( var i = 0; i < node.childNodes.length; i++ )
	{
		Wiki.HighlightSpaces( node.childNodes[i] );	
	}
	if( node.data ) node.data = node.data.replace( /\s+/gi, "%%%" );
	return;
}

if (typeof XMLHttpRequest == 'undefined') {
 XMLHttpRequest = function () {
   var msxmls = ['MSXML3', 'MSXML2', 'Microsoft'];
   for (var i=0; i < msxmls.length; i++) {
	 try {
	   return new ActiveXObject(msxmls[i]+'.XMLHTTP');
	 } catch (e) { }
   }
   throw new Error("No XML component installed");
 }
}




if (window.addEventListener) {
	window.addEventListener( "load", Wiki.ActivateElements, false );	
}
else if (window.attachEvent) {
	window.attachEvent( "onload", Wiki.ActivateElements );
}
else
{
	window.onload = Wiki.ActivateElements;
}
