var CRange = Class.create();
CRange.prototype = 
{
  initialize: function( wind ) 
  {
    this.wind = wind;
    this.doc = Element.extend( wind.document );
    this.container = this.doc.body;
        
    this.compareType = Try.these
    (
      function(){ return [ Range.START_TO_START, Range.END_TO_START, Range.START_TO_END, Range.END_TO_END ] },  // FF
      function(){ return [ "StartToStart", "StartToEnd", "EndToStart", "EndToEnd" ] }   // IE
    ) || false;
    this.isSelected = false;
  },
  
  activate: function()
  {
    if( this.isSelected )
      return;
    this.isSelected = true;  
    var selection = this.selection = this.getElemSelection( this.wind );

    this.range = Try.these
    (
      function(){ return selection.getRangeAt(0) },  // FF
      function(){ return selection.createRange() }   // IE
    ) || false;
//alert( 'range offset = ' + this.range.startOffset + ', ' + this.range.endOffset )
    this.container = Element.extend( this.getRangeContainer() );

    var r = Element.descendantOf( this.container, this.doc ); // is selection in this iframe?
//    alert(' r= ' + r )
    if( r )
    {
      this.setEndOffset();
      this.setStartOffset();
    }
    else
    {
      this.startOff = this.endOff = 0;
    }
    Try.these
    (
      function(){ selection.collapseToStart() },  // FF
      function(){ selection.empty() }   // IE
    );
    
    this.getNodesForProcessing();  
//alert( 'selection: from [' + this.startOff + '] to [' + this.endOff + '] text: {' + this.getText() + '}' )
  },

  getElemSelection: function( wind )
  {
    return Try.these
    (
      function(){ return wind.getSelection() },      // FF
      function(){ return wind.document.selection }   // IE
    ) || false;
  },
  
  getRangeForElement: function( domElement )
  {
    var _this = this;
    return Try.these
    (
      function()     // FF
      {
        var rangeToCheck = _this.doc.createRange();
        rangeToCheck.selectNode( domElement.firstChild );  // text Node
        return rangeToCheck 
      },
      function()     // IE
      { 
        var rangeToCheck = _this.doc.body.createTextRange();
        rangeToCheck.moveToElementText( domElement );
        return rangeToCheck 
      }
    ) || false;
  },

  getRangeContainer: function()
  {
    var r = this.range, t;
    var container = Try.these
    (
      function(){ (t = r.commonAncestorContainer).hasChildNodes(); return t }, // FF
      function(){ (t = r.parentElement()).hasChildNodes(); return t }          // IE
    ) || false;
  
//    alert( '1) tag name: ' + container.tagName + '; id: ' + container.id )
    while( container && !(container.tagName == 'P' || container.tagName == 'BODY') )
    {
      container = container.parentNode
//    alert( 'i) tag name: ' + container.tagName + '; id: ' + container.id )
    }
//    alert( 'ret) tag name: ' + container.tagName + '; id: ' + container.id )
    return container;
  },
// TODO: check if startContainer and endContainer have different parents !!!  
  getNodes: function()
  {
    var elements = new Array();
    var sibling = this.startContainer;

    while( sibling )
    {
      elements.push( sibling );
      if( sibling == this.endContainer )
        break;
      sibling = sibling.nextSibling;
    }
    
    return elements;
  },

  offsetDetermination: function( markElem )   // IE specific
  {
    var string = this.getTextAndBRFromElement( markElem.parentNode, markElem );
//    markElem.remove();  // ??? why it does not work in FF ???
    Element.remove( markElem );
//    alert( 'offsetDetermination: {' +string+'}' )
    return string.length;
  },
  
  getTextAndBRFromElement: function( node, stopElement )
  {
    var sibling = node.firstChild
    var string = '';
    
    while( sibling != stopElement )
    {
      if( sibling.nodeType == 3 )
        string += sibling.nodeValue;
      else
        if( sibling.nodeType == 1 && sibling.nodeName == 'BR'  )  
          string += '<br>';
        sibling = sibling.nextSibling;
    }
    return string;
  },

  getTextFromElement: function( node, stopElement )
  {
    var sibling = node.firstChild
    var string = '';
    
    while( sibling != stopElement )
    {
      if( sibling.nodeType == 3 )
        string += sibling.nodeValue;
      sibling = sibling.nextSibling;
    }
    return string;
  },

  setStartOffset: function()
  {
    var r = this.range, _this = this, markElem;
    Try.these
    (
      function()  // FF
      {
//        var rangeToMark = r.cloneRange();
//        rangeToMark.collapse(true);
        markElem = _this.doc.createElement( 'span' );
        markElem.id = 'mark';
//        markElem.innerHTML = '*';
//        rangeToMark.insertNode( markElem );
        r.insertNode( markElem );  // the operation resets corresponded selection, range and cloned range ?!?!
      }, 
      function()  // IE
      { 
        var rangeToMark = r.duplicate();
        rangeToMark.collapse(true);
        rangeToMark.pasteHTML( '<span id="mark">*</span>');
      }
    );
    markElem = Element.extend( this.doc.getElementById( 'mark' ) );
    this.startContainer = markElem.parentNode;
//alert( 'start>>> ' + this.doc.body.innerHTML );        
    this.startOff = this.offsetDetermination( markElem );
  },
  
  setEndOffset: function()
  {
    var r = this.range, _this = this, markElem;
    Try.these
    (
      function()  // FF
      {
        var rangeToMark = r.cloneRange();
        rangeToMark.collapse(false);
        markElem = _this.doc.createElement( 'span' );
        markElem.id = 'mark';
        rangeToMark.insertNode( markElem );
      }, 
      function()  // IE
      { 
        var rangeToMark = r.duplicate();
        rangeToMark.collapse(false);
        rangeToMark.pasteHTML( '<span id="mark">*</span>');
      }
    );
    markElem = Element.extend( this.doc.getElementById( 'mark' ) );
    this.endContainer = markElem.parentNode;
//alert( 'end>>> ' + this.doc.body.innerHTML + ' **** ' + r.endOffset );
    this.endOff = this.offsetDetermination( markElem );
  },
  
  getNodesForProcessing: function()
  {
//    var selText = this.getText();
    this.processingNodes = new Array();
    if( this.startOff == 0 && this.endOff == 0 )
      return;
    var elements = this.getNodes();
    var elementForProcessing;
    
//alert('>>> n=' + elements.length + '\n start=' + this.startOff + ' end=' + this.endOff )
    for( var i = 0; i < elements.length; i++ )
    {
      var elem =  Element.extend( elements[i] );
//      var str = elem.innerHTML.unescapeHTML()//.replace(/&nbsp;/g,' ');  // strips <BR>
      var str = elem.innerHTML.replace(/&nbsp;/g,' ').replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');  
      var substr;
//alert('i='+i+' html: {' + elem.innerHTML + '}\n str: {' + str +  '}' )
      if( i == 0 )
      { /* first element in selection */
        if( this.startOff == 0 && elements.length > 1)
        { // if startOff points to the begin of element : do nothing, just set styles
          elementForProcessing = elem;
        }
        else
          if( this.startOff == str.length )
          { // if startOff points to the end of element : skip processing
            elementForProcessing = null;
          }
          else 
          { // if startOff points inside element
            if( elements.length == 1 )  // selection inside one element
            {
              if( this.startOff == 0 )
              {
                substr = str.substring( this.startOff, this.endOff );
                this.editElement( substr, elem ); // no leading non-selected element, so use existing but cut his tail
                elementForProcessing = elem;
              }
              else
              {
                substr = str.substring( 0, this.startOff );  
                this.editElement( substr, elem );   // non-selected part of the element
                substr = str.substring( this.startOff, this.endOff );
                elementForProcessing = this.createNewElement( substr, elem.nextSibling, elem.cloneNode(true) );
              }
              substr = str.substring( this.endOff );  // the tail element
              if( substr.length > 0 )
                this.createNewElement( substr, elementForProcessing.nextSibling, elem.cloneNode(true) );
            }
            else
            {
              substr = str.substring( 0, this.startOff );  
              this.editElement( substr, elem );   // non selected part of the element

              substr = str.substring( this.startOff );  // the tail element
              if( substr.length > 0 )
                elementForProcessing = this.createNewElement( substr, elem.nextSibling, elem.cloneNode(true) );
              else
                elementForProcessing = null; 
            }  
          }
      }
      else
        if( i == (elements.length-1) )
        { /* last element in selection */
          substr = str.substring( 0, this.endOff );  
          if( substr.length > 0 )
          {
            elementForProcessing = this.createNewElement( substr, elem, elem.cloneNode(true) );
            substr = str.substring( this.endOff );
            this.editElement( substr, elem );
          }
          else
            elementForProcessing = null; 
        }
        else
        { /* middle elements in selection */
          elementForProcessing = elem;
        }
      if( elementForProcessing != null )
        this.processingNodes.push( elementForProcessing );      
    }   
  },
  
  applyCSSToSelection: function( style, value, toggleValue )
  {
    for( var i = 0; i < this.processingNodes.length; i++ )
    {
      var elementForProcessing = this.processingNodes[i];
      if( toggleValue != null )
      {
        var prevStyle = elementForProcessing.style[style];
        if( prevStyle && prevStyle == value )
          elementForProcessing.style[style] = toggleValue;
        else  
          elementForProcessing.style[style] = value;
      }
      else
        elementForProcessing.style[style] = value;
    }
  },
  
  applyFontSizeToSelection: function( sign )
  {
    for( var i = 0; i < this.processingNodes.length; i++ )
    {
      var elementForProcessing = this.processingNodes[i];
      var prevSize = elementForProcessing.style['fontSize'];
      if( prevSize == null || prevSize=='' )
        prevSize = 11;
//alert('fontSize=' + prevSize + '; ' + parseInt(prevSize) )
      elementForProcessing.style['fontSize'] = parseInt(prevSize) + sign + 'pt'
    }
  },
  
  createNewElement: function( text, elem, newEl )
  {
    var e;
    if( newEl )
    {
      e = newEl;  // if clone is used
      e.innerHTML = '';
    }
    else  
      e = this.doc.createElement( 'span' );
    Element.extend( e );
    var delim = (Prototype.Browser.IE)?'<BR>':'<br>';
    var array = text.split( delim );
    for( var i = 0; i< array.length; i++ )
    {
      var t = array[i];
      e.appendChild( this.doc.createTextNode( t ) );
      if( i < array.length - 1 )
        e.appendChild( this.doc.createElement( 'br' ) );
    }
    this.container.insertBefore( e, elem );
    
    return e;
  },
  
  editElement: function( text, elem )
  {
    elem.innerHTML = ''; // in other way ( elem.innerHTML = text ), IE trims leading spaces...
    var delim = (Prototype.Browser.IE)?'<BR>':'<br>';
    var array = text.split( delim );
    for( var i = 0; i< array.length; i++ )
    {
      var t = array[i];
      elem.appendChild( this.doc.createTextNode( t ) );
      if( i < array.length - 1 )
        elem.appendChild( this.doc.createElement( 'br' ) );
    }
  }
}
