/* @Packager.Header
<FileDescription>
	Class for working with a selection range, much like the W3C DOM Range, but
	it is not intented to be an implementation of the W3C interface.
	(IE Implementation)
</FileDescription>
<Author name="Frederico Caldeira Knabben" email="www.fckeditor.net" />
*/

FCKDomRange.prototype._UpdateElementInfo = function()
{
	if ( !this._Range )
		this.Release( true ) ;
	else
	{
		// Get a range for the start boundary.
		var oStartRange = this._Range.duplicate() ;
		oStartRange.collapse( true ) ;
		
		// Get a range for the end boundary.
		var oEndRange = this._Range.duplicate() ;
		oEndRange.collapse( false ) ;
		
		var eStart	= oStartRange.parentElement() ;
		var eEnd	= oEndRange.parentElement() ;
		
		var oElementPath = new FCKElementPath( eStart ) ;
		this.StartContainer		= oElementPath.LastElement ;
		this.StartBlock			= oElementPath.Block ;
		this.StartBlockLimit	= oElementPath.BlockLimit ;

		if ( eStart != eEnd )
			oElementPath = new FCKElementPath( eEnd ) ;
		this.EndContainer		= oElementPath.LastElement ;			
		this.EndBlock			= oElementPath.Block ;
		this.EndBlockLimit		= oElementPath.BlockLimit ;
	}
}

FCKDomRange.prototype.MoveToSelection = function()
{
	this.Release( true ) ;

	var oSel = this.Window.document.selection  ;
	
	if ( oSel.type != 'Control' )
	{
		this._Range = oSel.createRange() ;
		this._UpdateElementInfo() ;
	}
}

FCKDomRange.prototype.CheckIsEmpty = function()
{
	var bIsEmpty = false ;

	// Check if the range is empty or contains only spaces.
	var sTestRangeContents = this._Range.text ;
	if ( sTestRangeContents.Trim().length == 0 )
	{
		sTestRangeContents = this._Range.htmlText ;
		if ( !( bIsEmpty = ( sTestRangeContents == 0 ) ) )
		{
			// There must not be &nbsp; in the range, only "pure" spaces.
			if ( !(/(&nbsp;)|(<(IMG|OBJECT|EMBED|INPUT|SELECT|TEXTAREA))/i).test( sTestRangeContents ) )
				bIsEmpty = true ;
		}
	}
	// We'll be at the end of the block, if the range is empty or filled with spaces.
	return bIsEmpty ;
}

FCKDomRange.prototype.DeleteContents = function()
{
	if ( this._Range )
		this._Range.pasteHTML( '' ) ;
}

FCKDomRange.prototype.ExtractContents = function()
{
	if ( this._Range )
	{
		var oDocFrag = new FCKDocumentFragment( this.Window.document ) ;
		oDocFrag.AppendHtml( this._Range.htmlText ) ;
		this.DeleteContents() ;
		return oDocFrag ;
	}
}

FCKDomRange.prototype.CheckIsCollapsed = function()
{
	if ( this._Range )
		return ( this._Range.compareEndPoints( 'StartToEnd', this._Range ) == 0 ) ;
}

FCKDomRange.prototype.Collapse = function( toStart )
{
	if ( this._Range )
		this._Range.collapse( toStart ) ;
}

FCKDomRange.prototype.Clone = function()
{
	var oClone = FCKTools.CloneObject( this ) ;

	if ( oClone._Range )
		oClone._Range = oClone._Range.duplicate() ;
	
	return oClone ;
}

FCKDomRange.prototype.MoveToNodeContents = function( targetNode )
{
	if ( this._Range )
		this._Range.moveToElementText( targetNode ) ;
}

FCKDomRange.prototype.Select = function()
{
	if ( this._Range )
	{
		// TODO : Check the following
		if ( this.CheckIsCollapsed() && this.StartBlock && this.StartBlock.innerHTML.length == 0 )
		{
			this._Range.pasteHTML( '&nbsp;' ) ;
			this._Range.select() ;
		}
		else
			this._Range.select() ;
	}
}

FCKDomRange.prototype.InsertNode = function( node )
{
	if ( this._Range )
	{
		// Create a marker element in the start position of teh current range.
		var eMarker = this.InsertMarkerTag( true ) ;
		
		// Insert the node.
		FCKDomTools.InsertAfterNode( eMarker, node ) ;
		
		// Remove the marker.
		eMarker.parentNode.removeChild( eMarker ) ;
	}
}

FCKDomRange.prototype.InsertMarkerTag = function( toStart )
{
	// Get a range for the start boundary.
	var oRange = this._Range.duplicate() ;
	oRange.collapse( toStart === true ) ;
	
	// Paste a marker element at the collapsed range and get it from the DOM.
	var sMarkerId = 'fck_dom_range_temp_' + (new Date()).valueOf() + '_' + Math.floor(Math.random()*1000) ;
	oRange.pasteHTML( '<span id="' + sMarkerId + '"></span>' ) ;
	return this.Window.document.getElementById( sMarkerId ) ;
}

/*
 * Moves the position of the start boundary of the range to a specific position
 * relatively to a element.
 *		@position:
 *			1 = After Start		<target>^contents</target>
 *			2 = Before End		<target>contents^</target>
 *			3 = Before Start	^<target>contents</target>
 *			4 = After End		<target>contents</target>^
 */
FCKDomRange.prototype.SetStart = function( targetElement, position )
{
	var oRange = this._Range ;
	if ( !oRange )
	{
		oRange = this._Range = this.Window.document.body.createTextRange() ;
		oRange.collapse( true ) ;
	}
	
	var eTmpSpan = this.Window.document.createElement( 'span' ) ;
	eTmpSpan.id = 'fck_dom_range_temp_' + (new Date()).valueOf() + '_' + Math.floor(Math.random()*1000) ; 
	eTmpSpan.innerHTML = '&nbsp;' ;

	switch( position )
	{
		case 1 :		// After Start		<target>^contents</target>
			targetElement.insertBefore( eTmpSpan, targetElement.firstChild ) ;
		
			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( eTmpSpan ) ;
			oTargetRange.collapse( false ) ;
			
			if ( oRange.compareEndPoints( 'EndToStart', oTargetRange ) == -1 )
				oRange.setEndPoint( 'EndToStart', oTargetRange ) ;

			oRange.setEndPoint( 'StartToStart', oTargetRange ) ;
			break ;

		case 2 :		// Before End		<target>contents^</target>
			targetElement.appendChild( eTmpSpan ) ;
			
			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( eTmpSpan ) ;
			oTargetRange.collapse( true ) ;
			
			if ( oRange.compareEndPoints( 'EndToEnd', oTargetRange ) == -1 )
				oRange.setEndPoint( 'EndToEnd', oTargetRange ) ;

			oRange.setEndPoint( 'StartToEnd', oTargetRange ) ;
			break ;

		case 3 :		// Before Start		^<target>contents</target>
			targetElement.parentNode.insertBefore( eTmpSpan, targetElement ) ;
			
			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( eTmpSpan ) ;
			oTargetRange.collapse( true ) ;
			
			if ( oRange.compareEndPoints( 'EndToStart', oTargetRange ) == -1 )
				oRange.setEndPoint( 'EndToStart', oTargetRange ) ;

			oRange.setEndPoint( 'StartToStart', oTargetRange ) ;
			break ;
		
		case 4 :		// After End		<target>contents</target>^
			FCKDomTools.InsertAfterNode( targetElement, eTmpSpan ) ;

			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( targetElement ) ;
			oTargetRange.collapse( false ) ;
			
			if ( oRange.compareEndPoints( 'EndToEnd', oTargetRange ) == -1 )
				oRange.setEndPoint( 'EndToEnd', oTargetRange ) ;

			oRange.setEndPoint( 'StartToEnd', oTargetRange ) ;
	}
	
	if ( eTmpSpan.parentNode )
		eTmpSpan.parentNode.removeChild( eTmpSpan ) ;

	this._UpdateElementInfo() ;
}

/*
 * Moves the position of the start boundary of the range to a specific position
 * relatively to a element.
 *		@position:
 *			1 = After Start		<target>^contents</target>
 *			2 = Before End		<target>contents^</target>
 *			3 = Before Start	^<target>contents</target>
 *			4 = After End		<target>contents</target>^
 */
FCKDomRange.prototype.SetEnd = function( targetElement, position )
{
var oRange = this._Range ;
	if ( !oRange )
	{
		oRange = this._Range = this.Window.document.body.createTextRange() ;
		oRange.collapse( true ) ;
	}
	
	var eTmpSpan = this.Window.document.createElement( 'span' ) ;
	eTmpSpan.id = 'fck_dom_range_temp_' + (new Date()).valueOf() + '_' + Math.floor(Math.random()*1000) ; 
	eTmpSpan.innerHTML = '&nbsp;' ;

	switch( position )
	{
		case 1 :		// After Start		<target>^contents</target>
			targetElement.insertBefore( eTmpSpan, targetElement.firstChild ) ;
		
			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( eTmpSpan ) ;
			oTargetRange.collapse( true ) ;
			
			if ( oRange.compareEndPoints( 'StartToEnd', oTargetRange ) == 1 )
				oRange.setEndPoint( 'StartToEnd', oTargetRange ) ;

			oRange.setEndPoint( 'EndToEnd', oTargetRange ) ;
			break ;

		case 2 :		// Before End		<target>contents^</target>
			targetElement.appendChild( eTmpSpan ) ;
			
			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( eTmpSpan ) ;
			oTargetRange.collapse( false ) ;

			if ( oRange.compareEndPoints( 'StartToEnd', oTargetRange ) == 1 )
				oRange.setEndPoint( 'StartToEnd', oTargetRange ) ;

			oRange.setEndPoint( 'EndToEnd', oTargetRange ) ;
			break ;

		case 3 :		// Before Start		^<target>contents</target>
			targetElement.parentNode.insertBefore( eTmpSpan, targetElement ) ;
			
			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( eTmpSpan ) ;
			oTargetRange.collapse( false ) ;
			
			if ( oRange.compareEndPoints( 'StartToEnd', oTargetRange ) == 1 )
				oRange.setEndPoint( 'StartToEnd', oTargetRange ) ;

			oRange.setEndPoint( 'EndToEnd', oTargetRange ) ;
			break ;
		
		case 4 :		// After End		<target>contents</target>^
			FCKDomTools.InsertAfterNode( targetElement, eTmpSpan ) ;

			// Create a range for the element text.
			var oTargetRange = this.Window.document.body.createTextRange() ;
			oTargetRange.moveToElementText( targetElement ) ;
			oTargetRange.collapse( true ) ;
			
			if ( oRange.compareEndPoints( 'StartToEnd', oTargetRange ) == 1 )
				oRange.setEndPoint( 'StartToEnd', oTargetRange ) ;

			oRange.setEndPoint( 'EndToEnd', oTargetRange ) ;
	}
	
	if ( eTmpSpan.parentNode )
		eTmpSpan.parentNode.removeChild( eTmpSpan ) ;

	this._UpdateElementInfo() ;
}

//FCKDomRange.prototype.CreateBookmark = function()
//{
//	if ( this._Range )
//		return this._Range.getBookmark() ;
//}

//FCKDomRange.prototype.MoveToBookmark = function( bookmark )
//{
//	if ( !this._Range )
//		this._Range = this.Window.document.body.createTextRange() ;

//	this._Range.moveToBookmark( bookmark ) ;
//}

FCKDomRange.prototype.Expand = function( unit )
{
	switch ( unit )
	{
		case 'block_contents' :
			if ( this.StartBlock )
				this.SetStart( this.StartBlock, 1 ) ;
			else
			{
				// Create a marker element in the start position of the current range.
				var eMarker = this.InsertMarkerTag( true ) ;
				var oNode = eMarker ;

				// We must look for the left boundary, relative to the range
				// start, which is limited by a block element.
				while ( true )
				{
					var oSibling = oNode.previousSibling ;
					
					if ( !oSibling )
					{
						// Continue if we are not yet in the block limit (inside a <b>, for example).
						if ( oNode.parentNode != this.StartBlockLimit )
							oNode = oNode.parentNode ;
						else
							break ;
					}
					else if ( oSibling.nodeType != 1 || !(/^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/).test( oSibling.nodeName.toUpperCase() ) )
					{
						// Continue if the sibling is not a block tag.
						oNode = oSibling ;
					}
					else
						break ;
				}
				
				// Move the marker right before the resulting node.
				if ( oNode != eMarker )
					oNode.parentNode.insertBefore( eMarker.parentNode.removeChild( eMarker ), oNode ) ;

				// Move the range start to the marker.
				this.SetStart( eMarker, 4 ) ;
				
				// Remove the marker definitively.
				eMarker.parentNode.removeChild( eMarker ) ;
			}

			if ( this.EndBlock )
				this.SetEnd( this.EndBlock, 2 ) ;
			else
			{
				// Create a marker element in the end position of the current range.
				var eMarker = this.InsertMarkerTag( false ) ;
				var oNode = eMarker ;

				// We must look for the right boundary, relative to the range
				// end, which is limited by a block element.
				while ( true )
				{
					var oSibling = oNode.nextSibling ;
					
					if ( !oSibling )
					{
						// Continue if we are not yet in the block limit (inide a <b>, for example).
						if ( oNode.parentNode != this.EndBlockLimit )
							oNode = oNode.parentNode ;
						else
							break ;
					}
					else if ( oSibling.nodeType != 1 || !(/^(?:P|DIV|H1|H2|H3|H4|H5|H6|ADDRESS|PRE|OL|UL|LI|DT|DE)$/).test( oSibling.nodeName.toUpperCase() ) )
					{
						// Continue if the sibling is not a block tag.
						oNode = oSibling ;
					}
					else
						break ;
				}
				
				// Move the marker right before the resulting node.
				if ( oNode != eMarker )
					FCKDomTools.InsertAfterNode( oNode, eMarker.parentNode.removeChild( eMarker ) ) ;
				
				// Move the range before to the marker.
				this.SetEnd( eMarker, 3 ) ;
				
				// Remove the marker definitively.
				eMarker.parentNode.removeChild( eMarker ) ;
			}
		
			break ;										// @Packager.Remove.Start
		
		default :
			throw( 'Invalid unit "' + unit + '"' ) ;	// @Packager.Remove.End
	}
}