/* @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.
	(Gecko Implementation)
</FileDescription>
<Author name="Frederico Caldeira Knabben" email="www.fckeditor.net" />
*/

FCKDomRange.prototype._UpdateElementInfo = function()
{
	if ( !this._Range )
		this.Release( true ) ;
	else
	{
		var eStart	= this._Range.startContainer ;
		var eEnd	= this._Range.endContainer ;
		
		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.getSelection() ;
	
	if ( oSel.rangeCount == 1 )
	{
		// Let's work with the clone, otherwise changes in the range could
		// affect the current selection.
		this._Range = oSel.getRangeAt(0).cloneRange() ;
		this._UpdateElementInfo() ;
	}
}

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

	// Check if the range is empty or contains only spaces.
	var sTestRangeContents = this._Range.toString() ;
	if ( sTestRangeContents.Trim().length == 0 )
	{
		if ( !( bIsEmpty = ( sTestRangeContents.length == 0 ) ) )
		{
			// Inserts the contents of the range in a span tag.
			var eToolSpan = this.Window.document.createElement( 'span' ) ;
			eToolSpan.appendChild( this._Range.cloneContents() ) ;
			
			// There must not be &nbsp; in the range, only "pure" spaces.
			if ( !(/(&nbsp;)|(<(IMG|OBJECT|EMBED|INPUT|SELECT|TEXTAREA))/i).test( eToolSpan.innerHTML ) )
				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.deleteContents() ;
}

FCKDomRange.prototype.ExtractContents = function()
{
	if ( this._Range )
		return new FCKDocumentFragment( null, this._Range.extractContents() ) ;
}

FCKDomRange.prototype.CheckIsCollapsed = function()
{
	if ( this._Range )
		return this._Range.collapsed ;
}

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

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

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

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

FCKDomRange.prototype.Select = function()
{
	if ( this._Range )
	{
		var oSel = this.Window.getSelection() ;
		oSel.removeAllRanges() ;
		
		// We must add a clone otherwise Firefox will have rendering issues.
		oSel.addRange( this._Range.cloneRange() ) ;
	}
}

FCKDomRange.prototype.InsertNode = function( node )
{
	if ( this._Range )
		this._Range.insertNode( node ) ;
}

/*
 * 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.createRange() ;

	try
	{
		switch( position )
		{
			case 1 :		// After Start		<target>^contents</target>	
				oRange.setStart( targetElement, 0 ) ;
				break ;

			case 2 :		// Before End		<target>contents^</target>
				if ( targetElement.lastChild )
					oRange.setStartAfter( targetElement.lastChild ) ;
				else
					oRange.setStart( targetElement, 0 ) ;
				break ;

			case 3 :		// Before Start		^<target>contents</target>
				oRange.setStartBefore( targetElement ) ;
				break ;
			
			case 4 :		// After End		<target>contents</target>^
				oRange.setStartAfter( targetElement ) ;
		}
		this._UpdateElementInfo() ;
	}
	catch ( e )
	{
		// There is a bug in Firefox implementation (it would be too easy
		// otherwhise). The new start can't be after the end (W3C says it can).
		// So, let's create a new range and collapse it to the desired point.
		if ( e.toString().Contains( 'NS_ERROR_ILLEGAL_VALUE' ) )
		{
			this.Release( true ) ;
			this._Range = this.Window.document.createRange() ;
			this.SetStart( targetElement, position ) ;
		}
		else
			throw( e ) ;
	}
}

/*
 * 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.createRange() ;
	
	try
	{
		switch( position )
		{
			case 1 :		// After Start		<target>^contents</target>
				oRange.setEnd( targetElement, 0 ) ;
				break ;

			case 2 :		// Before End		<target>contents^</target>
				if ( targetElement.lastChild )
					oRange.setEndAfter( targetElement.lastChild ) ;
				else
					oRange.setEnd( targetElement, 0 ) ;
				break ;

			case 3 :		// Before Start		^<target>contents</target>
				oRange.setEndBefore( targetElement ) ;
				break ;
			
			case 4 :		// After End		<target>contents</target>^
				oRange.setEndAfter( targetElement ) ;
		}
		this._UpdateElementInfo() ;
	}
	catch ( e )
	{
		if ( e.toString().Contains( 'NS_ERROR_ILLEGAL_VALUE' ) )
		{
			// There is a bug in Firefox implementation (it would be too easy
			// otherwhise). The new start can't be after the end (W3C says it can).
			// So, let's create a new range and collapse it to the desired point.
			this.Release( true ) ;
			this._Range = this.Window.document.createRange() ;
			this.SetStart( targetElement, position ) ;
		}
		else
			throw( e ) ;
	}
}

FCKDomRange.prototype.Expand = function( unit )
{
	switch ( unit )
	{
		case 'block_contents' :
			if ( this.StartBlock )
				this.SetStart( this.StartBlock, 1 ) ;
			else
			{
				// Get the start node for the current range.
				var oNode = this._Range.startContainer ;
				
				// If it is an element, get the current child node for the range (in the offset).
				// If the offset node is not available, the the first one.
				if ( oNode.nodeType == 1 )
				{
					if ( !( oNode = oNode.childNodes[ this._Range.startOffset ] ) )
						oNode = oNode.firstChild ;
				}
				
				// Not able to defined the current position.
				if ( !oNode )
					return ;
				
				// 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 ;
				}
				
				this._Range.setStartBefore( oNode ) ;
			}
			
			if ( this.EndBlock )
				this.SetEnd( this.EndBlock, 2 ) ;
			else
			{
				var oNode = this._Range.endContainer ;
				if ( oNode.nodeType == 1 )
				{
					if ( !( oNode = oNode.childNodes[ this._Range.endOffset ] ) )
						oNode = oNode.lastChild ;
				}
				
				if ( !oNode )
					return ;
				
				// 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 ;
				}
				
				this._Range.setEndAfter( oNode ) ;
			}
		
		break ;											// @Packager.Remove.Start
		
		default :
			throw( 'Invalid unit "' + unit + '"' ) ;	// @Packager.Remove.End
	}
}