/*
////////////////////////////////////////////////////////////////////////
// MzInplace                                                          //
// v. 2.05                                                            //
// supported: MZ1.4+, MSIE5+                                          //
//                                                                    //
// (c) Nikolay "Kuso Mendokusee" Jaremko <mendokusee@pixel-apes.com>, //
//     2005                                                           //
//                                                                    //
// http://pixel-apes.com/mozilla-inplace                              //
//                                                                    //
////////////////////////////////////////////////////////////////////////

For license see LICENSE.TXT

HOW TO USE:
  Step 1.  Construct one MozillaInplace per textarea (you also need to supply an empty div called "holster"
  Step 2+. Call useful methods described below to gather required information

USEFUL METHODS:

  * getXY( id_of_html_element ) -- returns coordinates of any HTML element in pixels as [x,y] array

  * getHeight()     -- returns height of binded textarea in pixels
  * getHeightMore() -- same stuff, but adding some space for whitespace. And some issues like flickering and no downsizing

  * getCursor() -- returns coordinates of text cursor in binded textarea in pixels and symbols as [x,y, ss, se] array
                   x/y   -- are "msie-style" pixel coordinates
                   ss/se -- are "mozilla-style" symbol coordinates

  * getWordXY( word ) -- returns pixel & ss/se coordinates of all occurencies of a given word as [x,y, ss, se] array


  Also useful selection routines:

  * makeSelection( ss, se )  -- select some text inside of the binded textarea by [ss/se] "mozilla-style" symbol coordinates
  * replaceSelection( text ) -- replaces content of current selection inside of the binded textarea by a given text

*/
function MozillaInplace( textarea_id, holster_id )
{
  var isDOM = document.getElementById //DOM1 browser 
  var isO   = isO5 = window.opera && isDOM; //Opera 5+
  var isMZ  = isDOM && (navigator.appName=="Netscape")
  var isIE  = document.all && document.all.item && !isO // Microsoft Internet Explorer 4+

  this.isO8 = isO5; // !!!!
  this.isMZ = isMZ;
  this.isIE = isIE;
  this.bind( textarea_id, holster_id );

  this.sophisticatedRegExp = new RegExp( "^( +)", this.isMZ?"gmi":"gi" );

}

// --
// this method builds up textarea mirror for MZ. It is called from constructor.
// You could call it directly, if you want to rebind same Inplace class to another textarea.
MozillaInplace.prototype.bind = function(textarea_id, holster_id)
{

  this.t_id = textarea_id;
  this.h_id = holster_id;

  // next binding is MZ-only
  if (!this.isMZ && !this.isO8) return;

  // 1. build <div> struct
  var t = document.getElementById( this.t_id );
  var h = document.getElementById( this.h_id );

  h.className += " mozilla-inplace-holster "+t.className;
  var html = "<div class='mozilla-inplace-content' id='"+holster_id+"_content'></div>";

  var xy = this.getXY( this.t_id );
  var x = xy[0]; 
  var y = xy[1];

  h.style.position   = "absolute";
  h.style.overflow   = "hidden";
  h.style.top  = y + "px";
  h.style.left = (x-2000) + "px";
  h.innerHTML  = html;

  // 2. start mirroring
  this.getCursor();
}

// --
// you also could use this method to count coords of any HTML element in pixels
MozillaInplace.prototype.getXY = function( id )
{
  var z = document.getElementById( id );
  if (!z) return [-1,-1];
  var x=0;
  var y=0;
  do 
  {
    x += parseInt(isNaN(parseInt(z.offsetLeft))?0:z.offsetLeft);
    y += parseInt(isNaN(parseInt(z.offsetTop))?0:z.offsetTop);
  } 
  while (z=z.offsetParent);
  return [x,y];
}

// --
// do not use it. This method is called intrinsicly from getHeight to support MSIE way.
MozillaInplace.prototype._getHeight_ie = function( )
{
  var d = document.body;
  if ((document.compatMode) && (document.compatMode == "CSS1Compat")) d = document.documentElement;

  var t = document.getElementById( this.t_id );
  var _text = t.value;
  var range = t.createTextRange();
  var height = range.boundingHeight;
  return height;
}

// --
// Use this method to get height of text in textarea (in pixels)
MozillaInplace.prototype.getHeight = function( )
{
  if (this.isIE) return this._getHeight_ie();
  if (!this.isMZ && !this.isO8) return 0;

  // 1. mirror textarea
  var t = document.getElementById( this.t_id );
  var h = document.getElementById( this.h_id );
  var content = document.getElementById( this.h_id+"_content" );
  var text = t.value;
  var splitter4 = "~spli\04tter~";

  // format text like textarea does, by tricky regexps
  var dehtml = text.replace(/&/g, "&amp;")
                   .replace(/</g, "&lt;")
                   .replace(this.sophisticatedRegExp, splitter4+"$1"+splitter4)
                   .replace(/\n/g, "<br/>")
                   ;
  var dehtml_a = dehtml.split( splitter4 );
  var dehtml = "";
  for( var i=0; i < dehtml_a.length; i++)
  {
    if (i%2 == 0) dehtml+= dehtml_a[i];
    else          dehtml+= dehtml_a[i].replace(/ /g, "&nbsp;");
  }
  content.innerHTML = dehtml;

  // 2. resize "content" to get cursor coords
  var x = document.defaultView.getComputedStyle(t, null).getPropertyValue("width");
  content.style.width = (parseInt((x).substr(0,x.length-2),10)-2) + "px";

  // 3. get height
  var height_px = document.defaultView.getComputedStyle(content, null).getPropertyValue("height");
  return parseInt((height_px).substr(0,height_px.length-2),10);

}

// --
// Use this method to get height of text in textarea (in pixels)
// There is difference between this method and the previous one:
// this one adds some whitespace.
// It is somewhat simplier, but there is one subtle issue on it --
// it flicker. Also it could not downsize textarea back.
MozillaInplace.prototype.getHeightMore = function( )
{
  if (this.isIE) return this._getHeight_ie() + 50;
  if (!this.isMZ) return 0;

  var t = document.getElementById( this.t_id );
  var ss = t.selectionStart;
  var se = t.selectionEnd;
  var _value = t.value;
  t.value += "\n\n\n";

  var height = t.scrollHeight;

  t.value = _value;
  t.selectionStart = ss;
  t.selectionEnd   = se;
  return height;

}



// --
// do not use it. This method is called intrinsicly from getCursor to support MSIE way.
MozillaInplace.prototype.getCursor_ie = function( )
{
  var d = document.body;
  if ((document.compatMode) && (document.compatMode == "CSS1Compat")) d = document.documentElement;

  var sel = document.selection;
  var sel_range = sel.createRange();

  var t = document.getElementById( this.t_id );

  var left = d.scrollLeft + t.scrollLeft + sel_range.boundingLeft;
  var top  = d.scrollTop  + t.scrollTop  + sel_range.boundingTop;

  return [ left, top ];
}

// --
// The Main Method
// use this method to get [x,y] text cursor coords IN PIXELS
MozillaInplace.prototype.getCursor = function()
{
  if (this.isIE) return this.getCursor_ie();
  if (!this.isMZ) return;

  var t = document.getElementById( this.t_id );
  var ss=0, se=0;
  try // look, i have used a forbidden technique. Because MZ wave an exception if textarea is hidden somehow
  {
    ss   = t.selectionStart;
    se   = t.selectionEnd;
  } catch(e) { }
  return this._getXYbySE( ss, se );
}

// Insideous method (mz-only) to find pixel coordinates by ss/se pair (given in symbols)
MozillaInplace.prototype._getXYbySE = function( start, end )
{

  var t = document.getElementById( this.t_id );
  var h = document.getElementById( this.h_id );
  var content = document.getElementById( this.h_id+"_content" );

  // 1. mirror textarea content
  var splitter1 = "~spli\01tter~";
  var splitter2 = "~spli\02tter~";
  var splitter3 = "~spli\03tter~";
  var splitter4 = "~spli\04tter~";
  var ss   = start;
  var se   = end;
  var text = t.value;
  var left  = text.substr( 0, ss );
  var right = text.substr( ss );
  
  // now we will try to nobr around
  left  = left.replace( /(\S*)$/i, splitter2+"$1" );
  right = right.replace( /^(\S*)/i, "$1"+splitter3 );
  text = left + splitter1 + right;

  // format text like textarea does, by tricky regexps
  var dehtml = text.replace(/&/g, "&amp;")
                   .replace(/</g, "&lt;")
                   .replace(this.sophisticatedRegExp, splitter4+"$1"+splitter4)
                   .replace(/\n/g, "<br/>")
                   .replace(splitter1, "<span id='"+this.h_id+"_cursor' class='mozilla-inplace-cursor'></span>")
                   .replace(splitter2, "<nobr>")
                   .replace(splitter3, "</nobr>")
                   ;
  var dehtml_a = dehtml.split( splitter4 );
  var dehtml = "";
  for( var i=0; i < dehtml_a.length; i++)
  {
    if (i%2 == 0) dehtml+= dehtml_a[i];
    else          dehtml+= dehtml_a[i].replace(/ /g, "&nbsp;");
  }
  content.innerHTML = dehtml;

  // 2. resize "content" to get cursor coords
  var x = document.defaultView.getComputedStyle(t, null).getPropertyValue("width");
  content.style.width = (parseInt((x).substr(0,x.length-2),10)-2) + "px";
  var cursor = document.getElementById( this.h_id+"_cursor" );

  // 3. get "content" & "t" coords
  var contentXY = this.getXY( content.id );
  var tXY       = this.getXY( t.id );

  // N. return [ x, y ] struct
  return [
     (cursor.offsetLeft + tXY[0] - t.scrollLeft),
     (cursor.offsetTop  + tXY[1] - t.scrollTop ),
     ss,
     se
         ];
}

// --
// Use this method to find x/y coordinates of a given word 
MozillaInplace.prototype.getWordXY = function( word )
{
  var t = document.getElementById( this.t_id );
  var raw_data = " "+t.value.replace(/\r/ig, "")+" "; 

  // find matches in symbols
  var matches = [];
  var startIndex = 0;
  var currIndex = 0;
  var testRE = new RegExp( "^\\W[^ ]+\\W$", "" );
  while( currIndex >= 0)
  {
    currIndex = raw_data.indexOf( word, startIndex );
    startIndex = currIndex +1;
    if (currIndex >= 0)
    {
      // check if it is a single word
      var testStart = currIndex-1;
      var testLen = word.length+2;
      if (testStart < 0) testStart = 0;
      var testString = raw_data.substr( testStart, testLen );

      if (testRE.test(testString)) 
        matches[ currIndex-1 ] = currIndex-1;
    }
  }

  // IE return.
  if (this.isIE) return this._getMatchesXY_ie( word, matches );
  if (!this.isMZ) return [];

  //
  var pairs = [];
  for( var i in matches )
  {
    pairs[i] = this._getXYbySE( matches[i], matches[i]+word.length );
  }
  return pairs;
}

// --
// do not use it. This method is called intrinsicly from getWordXY to support MSIE way.
MozillaInplace.prototype._getMatchesXY_ie = function( word, matches )
{ 
  var d = document.body;
  if ((document.compatMode) && (document.compatMode == "CSS1Compat")) d = document.documentElement;
  var t = document.getElementById( this.t_id );

  // manipulate textarea
  var pairs = [];
  for( var i in matches )
  {
    var tr = t.createTextRange();
    tr.collapse(true);
    tr.moveStart( "character", matches[i]  );
    tr.moveEnd  ( "character", word.length );

    var left = d.scrollLeft + t.scrollLeft + tr.boundingLeft;
    var top  = d.scrollTop  + t.scrollTop  + tr.boundingTop;

    pairs[i] = [ left, top, matches[i], matches[i]+word.length ];
  }
  return pairs;
}


// ---
// Use this method to make selection from start/end position (in symbols)
MozillaInplace.prototype.makeSelection = function( ss, se )
{
  if (this.isIE) return this._makeSelection_ie( ss, se );
  if (!this.isMZ) return;

  var t = document.getElementById( this.t_id );
  t.selectionStart = ss;
  t.selectionEnd   = se;
}

// --
// do not use it. This method is called intrinsicly from makeSelection to support MSIE way.
MozillaInplace.prototype._makeSelection_ie = function( ss, se )
{ 
  var t = document.getElementById( this.t_id );
  var tr = t.createTextRange();
  tr.collapse(true);
  tr.moveStart( "character", ss );
  tr.moveEnd  ( "character", se-ss );
  tr.select();
  return tr;
}

// ---
// Use this method to replace selection with given text
MozillaInplace.prototype.replaceSelection = function( text )
{
  if (this.isIE) return this._replaceSelection_ie( text );
  if (!this.isMZ) return;

  var t = document.getElementById( this.t_id );
  t.value = t.value.substr( 0, t.selectionStart ) + text + t.value.substr( t.selectionEnd );
}

// --
// do not use it. This method is called intrinsicly from replaceSelection to support MSIE way.
MozillaInplace.prototype._replaceSelection_ie = function( text )
{ 
  var tr = document.selection.createRange();
  tr.text = text;
}

