//<!-- <script type="text/javascript"> -->

//=============================================================================
// Utilities

// test the browser model
var bIsNetscape = (navigator.appName == "Netscape");

function clrdebug()
{
	var oDbg = document.getElementById('debug');
	if ( oDbg ) oDbg.innerHTML = '';
}
function debug(txt)
{
	var oDbg = document.getElementById('debug');
	if ( oDbg ) oDbg.innerHTML += txt + '<br>'
}
function debugObj(o)
{
	for ( var i in o )
	{
		if ( bIsNetscape && (typeof(i) == 'string') )
		{
			if ( i.substr(0,4) == 'DOM_' ) continue;
		}
		debug( i + ':' + o[i] );
	}
}

//=============================================================================
// RichEdit constructor
function RichEdit(name, editStyle, charStyle, sText )
{
	// initialise some internal values
	this.bInFocus = false;

	// save the character style for later
	this.style = charStyle;

	// create an insertion point.
	//document.write('<div id="' + name + '"></div>');
	this.oInsertionPoint = document.getElementById(name);	

	// the 'text' box - or where the text goes
	this.oDiv = document.createElement('div');

	// set the styles
	this.copyStyle(this.oDiv, editStyle);
	this.oDiv.style.overflow = "auto";
	this.oDiv.style.wordWrap = "break-word";
	
	this.oDiv.setAttribute('id','paper-text');

	// insert the text
	if ( sText ) this.setHTML(sText);

	// link back to this object
	this.oDiv.oRichEdit = this;

	// handle clicks
	this.oDiv.onclick = RichEdit.prototype.onDivClick;
	if ( bIsNetscape )
	{
		// In Netscape, <div> elements don't have keyboard events, so
		// create a small, hidden text <input> box to catch them
		var oTextSpan = document.createElement('span');
		oTextSpan.style.position = 'absolute';
		oTextSpan.style.left = '-1000px';
		//oTextSpan.style.visibility = 'hidden';

		this.oTextbox = document.createElement('input');
		this.oTextbox.type = 'text';
		this.oTextbox.style.width = "1px";
		//this.oTextbox.style.border = 'none 0px';
		oTextSpan.appendChild(this.oTextbox);
		this.oInsertionPoint.appendChild(oTextSpan);

		// add event handlers
		this.oTextbox.oRichEdit  = this;
		this.oTextbox.onkeypress = RichEdit.prototype.onDivKeyPress;
		this.oTextbox.onfocus    = RichEdit.prototype.onDivFocus;
		this.oTextbox.onblur     = RichEdit.prototype.onDivBlur;
		//this.oTextbox.onkeyup    = RichEdit.prototype.onDivKeyUp;
	}
	else
	{
		// in IE, just attache everything to the <div>
		this.oDiv.onkeypress = RichEdit.prototype.onDivKeyPress;
		this.oDiv.onkeydown  = RichEdit.prototype.onDivKeyDown;
		this.oDiv.onfocus    = RichEdit.prototype.onDivFocus;
		this.oDiv.onblur     = RichEdit.prototype.onDivBlur;
		//this.oDiv.onkeyup    = RichEdit.prototype.onDivKeyUp;
	}

	// create the cursor
	this.oCursor = document.createElement('span');
	this.copyStyle(this.oCursor, this.style);
	this.oCursor.innerHTML = '|';

	//this.oCursor.style.position = 'absolute';
	//this.oCursor.style.verticalAlign = 'bottom';

	// <blink> is a Netscape thing, so... DIY
	var oCursor = this.oCursor;
	window.setTimeout(function(){RichEdit.prototype.onBlinkCursor(oCursor);},500);

	this.oInsertionPoint.appendChild(this.oDiv);
}
RichEdit.prototype.onBlinkCursor = function(oCursor)
{
	if ( oCursor.style.visibility == 'hidden' ) oCursor.style.visibility = 'visible';
	else oCursor.style.visibility = 'hidden';

	window.setTimeout(function(){RichEdit.prototype.onBlinkCursor(oCursor);},500);
}

RichEdit.prototype.focus = function()
{
	// grab the input focus
	if ( bIsNetscape ) this.oTextbox.focus();
	else this.oDiv.focus();
}

RichEdit.prototype.getCursorPos = function()
{
	// return an object describing the current cursor position:
	//    previous: the node before the cursor
	//    current: the current node
	//    next: the node following the cursor
	//    insert: A node to insert new nodes before. Not always the same as current
	if ( this.bInFocus )
	{
		var oParent = this.oCursor.parentNode;
		if ( oParent == this.oDiv )
		{
			// this can happen when an element other than <span> is current.
			var oCurrent = this.oCursor.nextSibling;
			var oNext = oCurrent ? oCurrent.nextSibling : null;
			return {prev:this.oCursor.previousSibling,current:oCurrent,next:oNext,insert:this.oCursor};
		}
		if ( !oParent )
		{
			return {prev:this.oDiv.lastChild,current:null,next:null,insert:null};
		}
		return {prev:oParent.previousSibling,current:oParent,next:oParent.nextSibling,insert:oParent};
	}

	if ( !this.oLastCursorPos )
	{
		this.oLastCursorPos = {prev:this.oDiv.lastChild,current:null,next:null,insert:null};
	}
	return this.oLastCursorPos;
}
RichEdit.prototype.assimilateStyle = function(oElt)
{
	if ( !oElt ) oElt = this.oDiv.lastChild;

	// when the cursor moves, it is natural that it should take on
	// the style of its surroundings
	while ( oElt && oElt.previousSibling && (!oElt.bHasStyle || (oElt == this.oCursor)) )
	{
		oElt = oElt.previousSibling;
	}
	if ( oElt && oElt.tagName.toLowerCase() == 'span' )
	{
		this.setStyle(oElt.style);
		if ( this.onstylechange != undefined ) this.onstylechange(this.style);
	}
}
RichEdit.prototype.setCursorPos = function(oSpan, bCopyStyle)
{
	// set the position of the cursor to just before oSpan
	// or if null/undefined, append to the end.
	var oElt;
	if ( oSpan )
	{
		if ( this.bInFocus )
		{
			if ( oSpan.tagName.toLowerCase() != 'span' ) this.oDiv.insertBefore(this.oCursor, oSpan);
			else if ( oSpan.firstChild ) oSpan.insertBefore(this.oCursor, oSpan.firstChild);
			else oSpan.appendChild(this.oCursor);
		}
		else this.oLastCursorPos = {prev:oSpan.previousSibling,current:oSpan,next:oSpan.nextSibling,insert:oSpan};
		oElt = oSpan;
	}
	else
	{
		oElt = this.oDiv.lastChild;
		if ( this.bInFocus ) this.oDiv.appendChild(this.oCursor);
		else this.oLastCursorPos = null;
	}

	if ( (bCopyStyle == undefined) || (bCopyStyle == true) ) this.assimilateStyle(oElt);
}

RichEdit.prototype.onBlur = function()
{
	// when focus goes somewhere else, record the last position
	// and hide the cursor
	this.oLastCursorPos = this.getCursorPos();

	// insert can sometimes be the cursor itself and since this
	// is being removed, copy current.
	this.oLastCursorPos.insert = this.oLastCursorPos.current;

	this.bInFocus = false;
	this.oCursor.parentNode.removeChild(this.oCursor);
}
RichEdit.prototype.onFocus = function()
{
	// focus has returned, so re-insert the cursor
	var oPos = this.getCursorPos();
	this.bInFocus = true;
	this.setCursorPos(oPos.current, false);
}
RichEdit.prototype.copyStyle = function(oElt, style, template)
{
	// the template is used to determine what to copy
	if ( !template ) template = style;

	// set the styles
	for ( var iStyle in template )
	{
		if ( style[iStyle] != undefined ) oElt.style[iStyle] = style[iStyle];
	}
	oElt.bHasStyle = true;
}
RichEdit.prototype.setStyle = function(style)
{
	this.copyStyle(this, style, this.style);
	this.copyStyle(this.oCursor, style, this.style);
}
RichEdit.prototype.compareStyle = function(oSpan1, oSpan2)
{
	// return true if the style of the spans is equivalent
	var style1 = oSpan1.style;
	var style2 = oSpan2.style;

	// equivalence depends only on the styles we are interested in.
	for ( var iStyle in this.style )
	{
		if ( style1[iStyle] !== style2[iStyle] ) return false;
	}
	return true;
}
RichEdit.prototype.insertNode = function(oNode)
{
	// insert a node at the current insertion point.
	var oPos = this.getCursorPos();
	if ( oPos.insert ) {
		this.oDiv.insertBefore(oNode, oPos.insert);
	} else {
		this.oDiv.appendChild(oNode);
	}

	// any node inserted into the document must notify the control
	// when it is clicked on so that the cursor may be moved
	oNode.onclick = RichEdit.prototype.onSpanClick;
	oNode.oRichEdit = this;
}
RichEdit.prototype.insertText = function(sText, oStyle)
{
	// insert a text string
	if ( !oStyle ) oStyle = this.style;

	var n = sText.length;
	for ( var i = 0; i < n; i++ )
	{
		// for each character, insert a new <span> element with current style
		var oSpan = document.createElement('div');
		oSpan.className = 'char';
		//this.copyStyle(oSpan, oStyle, this.style);
		var c = sText.charAt(i);

		// not all keyboard characters translate well to HTML text
		switch ( c )
		{
		case '\n':	oSpan = document.createElement('br');	break;
		case ' ':	oSpan.innerHTML = '&nbsp;';				break;
		case '<':	oSpan.innerHTML = '&lt;';				break;
		case '>':	oSpan.innerHTML = '&gt;';				break;
		case '&':	oSpan.innerHTML = '&amp;';				break;
		case '"':	oSpan.innerHTML = '&quot;';				break;
		case "'":	oSpan.innerHTML = '&#39;';				break;
		default:	oSpan.innerHTML = c;					break;
		}
		this.insertNode(oSpan);
	}
}
RichEdit.prototype.onKeyPress = function(evt)
{
	// handle keyboard events

	// in Netscape, handle movement keys here
	if ( bIsNetscape && this.onKeyDown(evt) ) return;

	var keyCode = evt.keyCode;
	
	if ( keyCode == 13 ) // return
	{
		// insert a line break before the cursor
		this.insertNode(document.createElement('br'));
	}
	else if ( ( keyCode == 8 || keyCode == 32 ) && navigator.appName == 'Opera' ) {
		if(keyCode == 32){
			this.insertText(' ');
		}
		document.onkeypress = function() // opera
		{
			switch(event.keyCode){
				case 8:
				case 32:
					return false;
			}
			return true;
		}
	}
	else
	{
		// insert the character before the cursor
		this.insertText(String.fromCharCode(bIsNetscape ? evt.which : evt.keyCode))
	}

	// keep the cursor in view.
	this.seeCursor();
}
/*
RichEdit.prototype.onKeyUp = function(evt){
	
} 
*/

RichEdit.prototype.onKeyDown = function(evt)
{
	var bRet = true;
	var keyCode = evt.keyCode;

	// find the cursor
	var oPos = this.getCursorPos();

	var oPrev = oPos.prev;
	var oNext = oPos.next;
	var oNext2, oPrev2;
	var nLeft = this.oCursor.offsetLeft;
	var nTop  = this.oCursor.offsetTop;
	
	switch ( keyCode )
	{
	case 37: // left arrow
		if ( oPrev ) this.setCursorPos(oPrev);
		else this.setCursorPos(this.oDiv.firstChild);
		break;
	case 39: // right arrow
		this.setCursorPos(oNext);
		break;
	case 38: // up arrow
		// search backwards for a character above and to the left of this one
		while ( oPrev )
		{
			oPrev2 = oPrev.previousSibling;
			if ( !oPrev2 )
			{
				this.setCursorPos(oPrev);
				break;
			}
			if ( (oPrev.tagName.toLowerCase() == 'br') && (oPrev2.offsetLeft < nLeft) )
			{
				this.setCursorPos(oPrev);
				break;
			}
			if ( (oPrev2.tagName.toLowerCase() != 'br') && (oPrev2.offsetTop < nTop) && (oPrev2.offsetLeft <= nLeft) )
			{
				this.setCursorPos(oPrev2);
				break;
			}
			oPrev = oPrev2;
		}
		break;
	case 40: // down arrow
		// search forewards for a character just below the cursor
		while ( oNext )
		{
			oNext2 = oNext.nextSibling;
			if ( !oNext2 )
			{
				this.setCursorPos();
				break;
			}
			if ( oNext2.offsetTop > nTop )
			{
				if ( oNext2.offsetLeft >= nLeft )
				{
					this.setCursorPos(oNext2);
					break;
				}
				// special case, end of the next line
				if ( (oNext2.offsetTop > oNext.offsetTop) && (oNext.tagName.toLowerCase() != 'br') )
				{
					this.setCursorPos(oNext2);
					break;
				}
				if ( oNext2.tagName.toLowerCase() == 'br' )
				{
					this.setCursorPos(oNext2);
					break;
				}
			}
			oNext = oNext2;
		}
		break;
	case 8 : // backsp
		if ( oPrev )
		{
			this.oDiv.removeChild(oPrev);
			this.assimilateStyle(oPos.current);
			document.onkeydown = function() // IE
			{
				var kc = event.keyCode;
				return kc != 8;
			}
		}
		break;
	case 46: // del
		if ( oPos.current )
		{
			this.oDiv.removeChild(oPos.current);
			this.setCursorPos(oPos.next);
		}
		break;
	case 36: // home
		if ( evt.ctrlKey ) this.setCursorPos(this.oDiv.firstChild);
		else
		{
			// search backwards to the beginning of the line
			oPrev = oPos.current;
			while ( oPrev )
			{
				if ( oPrev.offsetLeft == 0 )
				{
					this.setCursorPos(oPrev);
					break;
				}
				oPrev = oPrev.previousSibling;
			}
			if ( !oPrev ) this.setCursorPos(this.oDiv.firstChild);
		}
		break;
	case 35: // end
		if ( evt.ctrlKey ) this.setCursorPos();
		else
		{
			// search forwards to the end of the line
			while ( oNext )
			{
				if ( (oNext.tagName.toLowerCase() == 'br') || (oNext.offsetTop > nTop) )
				{
					this.setCursorPos(oNext);
					break;
				}
				oNext = oNext.nextSibling;
			}
			if ( !oNext ) this.setCursorPos();
		}
		break;
	case 33: // page up
		// implementation left as exercise for the reader
		break;
	case 34: // page down
		// implementation left as exercise for the reader
		break;
	default:
		bRet = false;
		break;
	}

	this.seeCursor();
	if ( bIsNetscape ) return bRet;
}
RichEdit.prototype.seeCursor = function()
{
	var oPos = this.getCursorPos();
	if ( !oPos.insert ) return;

	var sh = this.oDiv.scrollHeight;
	var st = this.oDiv.scrollTop;
	var ot = oPos.insert.offsetTop;

	var dh = this.oDiv.offsetHeight;
	var oh = this.oCursor.offsetHeight;

	// st should be less than ot
	// and greater than ot + oh - dh
	if ( st > ot ) this.oDiv.scrollTop = ot;
	else if ( st < (ot + oh - dh) ) this.oDiv.scrollTop = ot + oh - dh;
}
RichEdit.prototype.getScratchPad = function()
{
	// get a hidden <div> scratch-pad to work on
	if ( !this.oPad )
	{
		this.oPad = document.createElement('div');
		this.oPad.style.position = 'absolute';
		this.oPad.style.visibility = 'hidden';
		this.oInsertionPoint.appendChild(this.oPad);
	}
	else
	{
		// clear anything that might have been there
		this.oPad.innerHTML = '';
	}
	return this.oPad;
}
RichEdit.prototype.getHTML = function()
{
	// return a compact version of HTML contents in text form

	// remove the cursor first
	var oCursorPos = this.getCursorPos();
	if ( this.bInFocus )
	{
		this.oDiv.removeChild(this.oCursor);
	}

	var oPad = this.getScratchPad();

	// iterate through each character-element or image
	// and merge into the scratch-pad
	var oElt = this.oDiv.firstChild;
	var oPost = null;
	while ( oElt )
	{
		if ( oPost &&
			(oPost.tagName.toLowerCase() == 'span') &&
			(oElt.tagName.toLowerCase() == 'span') &&
			this.compareStyle(oPost,oElt) )
		{
			// styles are the same, so merge
			oPost.innerHTML += oElt.innerHTML;
		}
		else
		{
			oPost = oElt.cloneNode(true);
			oPad.appendChild(oPost);
		}
		oElt = oElt.nextSibling;
	}

	if ( this.bInFocus ) this.setCursorPos(oCursorPos.current);

	return oPad.innerHTML;
}
RichEdit.prototype.setHTML = function(sHTML)
{
	this.oDiv.innerHTML = '';
	this.oLastCursorPos = null;
	this.bInFocus = false;

	// get a hidden scratchpad to work on
	var oPad = this.getScratchPad();
	oPad.innerHTML = sHTML;

	// iterate through each element of oPad and extract text, <br>, <img>, etc
	var oElt = oPad.firstChild;
	if ( oElt.nodeType == 3 ) // text
	{
		this.insertText(sHTML);
	}
	else
	{
		while ( oElt )
		{
			var oNext = oElt.nextSibling;
			if ( oElt.tagName.toLowerCase() == 'span' )
			{
				// must convert special HTML codes back to text so they
				// can be translated properly by insertText()
				var sText = oElt.innerHTML.replace('&amp;','&');
				sText = sText.replace('&quot;','"');
				sText = sText.replace('&nbsp;',' ');
				sText = sText.replace('&lt;','<');
				sText = sText.replace('&gt;','>');
				sText = sText.replace('&#39;',"'");

				// insert the text from the element with the element's style
				this.insertText(sText, oElt.style);
			}
			else
			{
				// don't know what it is, so insert it verbatim
				this.insertNode(oElt);
			}

			oElt = oNext;
		}
	}
}

//=============================================================================
// Textbox event handlers

/*
RichEdit.prototype.onDivKeyUp = function(e)
{
	var evt = e ? e : window.event;
	this.oRichEdit.onKeyUp(evt);
}
*/

RichEdit.prototype.onDivKeyDown = function(e)
{
	// fetch event object
	var evt = e ? e : window.event;
	this.oRichEdit.onKeyDown(evt);
}
RichEdit.prototype.onDivKeyPress = function(e)
{
	// fetch event object
	var evt = e ? e : window.event;
	this.oRichEdit.onKeyPress(evt);
}
RichEdit.prototype.onDivFocus = function()
{
	this.oRichEdit.onFocus();
}
RichEdit.prototype.onDivBlur = function()
{
	this.oRichEdit.onBlur();
}
RichEdit.prototype.onDivClick = function()
{
	if ( !this.oRichEdit.bInFocus )
	{
		this.oRichEdit.setCursorPos();
		this.oRichEdit.focus();
	}
}
RichEdit.prototype.onSpanClick = function()
{
	this.oRichEdit.setCursorPos(this);
	this.oRichEdit.focus();
}
RichEdit.prototype.insertImage = function(src)
{
	// insert a new <img> element at the current position
	var oImg = document.createElement('img');
	oImg.src = src;

	this.insertNode(oImg);
}


//</script>

