function Document_Range( p_oRichText, p_bEmptyRange )
{
	this.oRange	= null;
	this.oRichText	= p_oRichText;
	
	this.getRTE			= function ( )	{	return this.oRichText.getRTE( );			}
	this.getDocument	= function ( )	{	return this.oRichText.getDocument( );		}
	this.getSelection	= function ( )	{	return this.oRichText.getSelection( );		}
	this.getDOM			= function ( )	{	return DOM;			}
	this.select			= function ( )	{	
		/* Try/catch because on peut changer de bloc de contenu et du coup, le savedrange lève une exception dans IE  */
		try {
			if ( ie4 )
				this.oRange.select( );	
			else
				this.getSelection( ).addRange( this.oRange );
		}
		catch( e )
		{
		}
	}
	
	this.execCommand = function( p_strCommand, p_bUserInterface, p_Value )		
	{	
		if ( ie4 )
		{
//			this.getSelection( ).execCommand( p_strCommand, p_bUserInterface, p_Value ); 
		}
		// all version :
		this.getDocument( ).execCommand( p_strCommand, p_bUserInterface, p_Value ); 
	}
	
	this.createRange = function ( p_oSelection )
	{ 
		// --- si on crée un range à partir d'une selection ---
		if ( p_oSelection )
		{
			if ( ie4 )
			{
				this.oRange = p_oSelection.createRange( );
			}
			else if ( p_oSelection.rangeCount > 0 )
			{
				try {
					this.oRange = p_oSelection.getRangeAt( p_oSelection.rangeCount - 1 ).cloneRange( );
					//this.oRange = p_oSelection.getRangeAt( 0 );
				} catch(e) {
					this.oRange = document.createRange();
				}
			}
		}
		
		// --- sinon on crée un range vide ---
		if ( ! this.oRange )
		{
			if ( ie4 )
			{
				this.oRange = this.getDocument( ).body.createTextRange( );
			}
			else
			{
				this.oRange = this.getDocument( ).createRange( );	
			}
		}
	}
	
	
	// --- Cree une selection autour de tous les enfants du noeud désiré ---
	this.selectionNode = function ( p_oNode )
	{
		if ( ie4 )
		{
			this.oRange.moveToElementText( p_oNode );
			this.select( );
		}
		else
		{
			this.getSelection( ).selectAllChildren( p_oNode );
		}
	}
	
	// Fonction qui indique si le tag p_strTagName exite dans les parents de p_oNode, en tenant compte du fait qu'un span / td / div, casse la chaine
	this.isAlreadyTagged = function ( p_oNode, p_strTagName )
	{
		var oDomPath = this.getDOM( ).getPath( p_oNode );
		var aNodes = oDomPath.getNodes( );
		var i = 0;
		
		p_strTagName = p_strTagName.toUpperCase( );
		
		for ( i = aNodes.length - 1 ; i >= 0 ; i-- )
		{
			if ( aNodes[ i ] )
			{
				// Si on trouve deja un tag contenant celui qu'on veut inserer on indique qu'on l'a trouvé
				if ( p_strTagName == aNodes[ i ].nodeName.toUpperCase( ) )
				{
					return true;
				}
				// Si par contre on trouve un tag empechant l'héritage du tag à inserer, on indique qu'il peut etre inseré
				else if ( p_strTagName == "SPAN" ||  p_strTagName == "DIV" || p_strTagName == "TD" )
				{
					return false;
				}
			}
		}
		return false;		
	}
	
	this.deleteContents = function( )
	{
		if ( ie4 )
		{
			this.oRange.execCommand( "Delete" );
		}
		else
		{
			this.oRange.deleteContents( );
		}
	}

	// fonction qui renvois les noeuds sélectionnés //
	this.getContents = function( )
	{
		var aNodes = Array( );
		alert( this.oRange.extractContents( )[ 0 ] );
	}
	
	
	// fonction qui renvoie une copie du contenu ( documentFragment ou html ) selectionné //
	this.getCopyContents = function( b_HTML )
	{
		var fragment;
		
		if ( ie4 )
		{
			fragment = this.getDOM( ).createDocumentFragmentFromHTML( this.getDocument( ), this.oRange.htmlText );
		}
		else
		{
			fragment = this.oRange.cloneContents( );
		}
		
		if ( b_HTML )
		{
			return DOM_outerHTML( fragment );
		}
		
		return fragment;
	}
	
	this.extractContents = function( b_HTML )
	{
		var htmlOrFragment = "";
		
		htmlOrFragment = this.getCopyContents( b_HTML );
		this.deleteContents( )
		
		return htmlOrFragment;
	}
	
	this.replaceContents = function( p_sHtml )
	{
		this.deleteContents( )
		this.pasteHTML( p_sHtml );
	}
	
	
	this.appendXML = function( p_oNode, p_sXML )
	{
		var oRootNodeCreated = DOM_htmlToNodes( p_sXML, true, this.getDocument( ) )
		if ( oRootNodeCreated )
			p_oNode.appendChild( oRootNodeCreated );
		else
			__DEBUG__ ( "Document_Range::appendXML la fonction n'as pas pu inserer le XML car il n'a pas pu etre cree" )
	}
	
	
	this.changeClassName = function( p_sClassName )
	{
		this.pasteHTML( "<span class=\"" + p_sClassName + "\">" + DOM_outerText( this.getCopyContents( false ) ) + "</span>" );
	}
	
	// retourne le premier noeud créé //
	this.pasteHTML = function ( html )
	{
		var i = 0;
		
		if ( ie4 ) // trop facile ...
		{
//			alert( "pasteHTML for IE4 ( document_range.js ) est disable !" );
			this.oRange.pasteHTML( html );
			// BUG ???
			// A VIRER ALEX
		}
		else
		{
			var sel = this.getSelection( );
			var rng = this.oRange;
			var node = rng.createContextualFragment( html );
			
			var newRng = null;
			var containerNode, startPos;
	
			if ( !sel || !rng )		return;
	
			this.deleteContents( );
			this.removeSelection( );
			
			containerNode	= this.getCurrentNode( );
			startPos		= rng.startOffset;
			newRng		= new Document_Range( this.oRichText, true );
			
			// --- Si on insert qu'un simple texte dans un autre, on peut optimiser ---
			if ( RTE_isTextNode( containerNode ) && RTE_isTextNode( node ) )
			{
				containerNode.insertData( startPos, node.nodeValue );
				
				// --- On place le curseur à la fin du texte inséré ---
				newRng.setEnd( containerNode, startPos + node.nodeValue.length );
				newRng.setStart( containerNode, startPos + node.nodeValue.length );
			}
			else
			{
				// --- Si on insert un noeud à l'interieur d'un texte
				// on doit deja splité le texte en 2 noeud texte et inséré le nouveau entre les 2
				if ( RTE_isTextNode( containerNode ) )
				{
					var textNode	= containerNode;
					var strTexte	= textNode.nodeValue;
	
					var strTexte1	= strTexte.substr( 0, startPos );
					var strTexte2	= strTexte.substr( startPos );
					
					var objNode1	= this.getDOM( ).createTextNode( this.getDocument( ), strTexte1 );
					var objNode2	= this.getDOM( ).createTextNode( this.getDocument( ), strTexte2 );
					
					containerNode	= textNode.parentNode;
					// --- on ajoute les 2 nouveaux noeuds texte ---
					containerNode.insertBefore( objNode2, textNode );

					var tmpNode = node.firstChild;
					var tmpOldNode = objNode2;
					
					containerNode.insertBefore( objNode1, textNode );
					tmpOldNode = containerNode.insertBefore( node, textNode );
					containerNode.insertBefore( objNode2, textNode );

					// --- on termine par supprimer le noeud courant ---
					containerNode.removeChild( textNode );
				}
				else
				{
					var targetNode = containerNode.childNodes[ startPos ];
					containerNode.insertBefore( node, targetNode );
				}
			}
		}
	}
	
	
	this.getContainerNode = function( )
	{
		if ( ie4 )		return this.oRange.parentElement( );
		else			return this.oRange.startContainer.parentNode;	// ATTENTION  : Répercution sévères !!!
//		//getSelection( ).focusNode;
	}

	/*
		NE PAS TROP TOUCHER, EN CAS DE RECHERCHE D'UN NOEUD CREE, UTILISER this.parentElement( ).getElementsByTagName( "A")[0]
	*/
	this.parentElement = function( )
	{
		if ( ie4 ) 
		{
			switch ( this.getSelection( ).type ) 
			{
				case "Text":
				case "None":	return this.oRange.parentElement( );
				case "Control":	return this.oRange.item( 0 );
				default:		return this.getDocument( );
			}
		} 
		else 
		{
			var oNode = this.oRange.commonAncestorContainer;
			if (!this.oRange.collapsed && this.oRange.startContainer == this.oRange.endContainer &&
				this.oRange.startOffset - this.oRange.endOffset <= 1 && this.oRange.startContainer.hasChildNodes() )
				oNode = this.oRange.startContainer.childNodes[this.oRange.startOffset];
	
			while ( oNode.nodeType == 3 ) oNode = oNode.parentNode;
			return oNode;
		}	
//		if ( ie4 )		return this.oRange.parentElement( );
//		else			return this.oRange.startContainer;			// ATTENTION  : Répercution sévères !!!
	}
	

	this.getCurrentNode = function( )
	{
		if ( ie4 )		alert( "IE Spec : A definir dans dom.js" ); //return this.oRange.parentElement( )
		else			return this.oRange.startContainer;
//		else			return this.oRange.startContainer; //getSelection( ).focusNode;
	}
	

	this.removeSelection = function ( )
	{
		if ( ie4 ) 
		{
					// inconnu
		} 
		else
		{
			var sel = this.getSelection( );
			if ( sel )	sel.removeAllRanges( );
		}
	}
	
	

	// --- [ WRAPPER ] --------------------------------------------------------------

	// --- ie & mozilla ---	
	this.collapse = function( p_bToStartPos )
	{
		if ( !p_bToStartPos ) p_bToStartPos = false;
		this.oRange.collapse( p_bToStartPos );
	}
	
	// --- Mozilla only ---
	this.selectNode = function( p_oNode )
	{	
		var oSel = this.getSelection( );
		this.oRange.selectNode( p_oNode );
		oSel.removeAllRanges( );
		oSel.addRange( this.oRange );
//		this.oRange.selectNode( p_oNode );
	}
	
	this.selectNodeContents = function( p_oNode )			
	{	
		var oSel = this.getSelection( );
		this.oRange.selectNodeContents( p_oNode );			
		oSel.removeAllRanges( );
		oSel.addRange( this.oRange );
//		this.oRange.selectNode( p_oNode );
	}
	this.surroundContents = function( p_oNode )				{	this.oRange.surroundContents( p_oNode );			}
  	this.setStart = function ( p_oParentNode, p_offset )	{	this.oRange.setStart ( p_oParentNode, p_offset );	}
  	this.setEnd = function ( p_oParentNode, p_offset )		{	this.oRange.setEnd ( p_oParentNode, p_offset );		}
	
	
	// --- [ CONSTRUCTEUR ] --------------------------------------------------------------
	
	if ( !this.getDocument( ) )
	{
		__DEBUG__( "Document_Range::Constructor - Attention, l'objet document n'as pas été passé, le document courrant va etre utilisé" );
		this.oDocument = document;
	}
	if ( !p_bEmptyRange )
	{
		this.createRange( this.getSelection( ) );
	}
	else
	{
		this.createRange( null );
	}
}


function RTE_isTextNode( node )
{
	if ( ie4 )	return ( node.nodeType == 3 );		// A MAJ
	else		return ( node.nodeType == 3 );
}


