/**
 ** Javascript routines to support the BrushedTemplate 
 ** and the editors/plain.jsp
 ** Needs brushed.js and prototype.js
 **
 ** EditTools object (main object)
 **     - section support : view and edit specific sections of a page
 **     - editor toolbar : insert wiki-markup with a simple button
 **     - find&replace functionality : with regexp support
 **     - metadat editor : page ACLs and variables
 **
 ** TextArea object
 **     Supports selections inside textarea, in ie and other browsers
 **/

var EditTools = 
{
  SelectID      : "sectionSelector",
  MainareaID    : "editorarea",
  WorkareaID    : "workarea",

  MetaID        : "meta",
  PermissionID  : "permissions",
  MetadataID    : "metadata",

  ToolbarID     : "tb",
  ToolbarMarker : "mark",      //css class to mark activate buttons
  FindTextID    : "tbFIND",
  ReplaceTextID : "tbREPLACE",
  RegExpID      : "tbREGEXP",
  GlobalID      : "tbGLOBAL",
  MatchCaseID   : "tbMatchCASE",
  UndoID        : "tbUNDO",
  RedoID        : "tbREDO",
  PasteID       : "tbPASTE",  
  ColorID       : "tbColor",  
  BackgroundID  : "tbBackground",  
  
  mainarea      : null,
  workarea      : null,
  textarea      : null,  //either mainarea or workarea
  selector      : null,
  metasize      : 0, // length of meta definitions (ALLOW and SET markup)

  workareaStart : 0, //start index of section in the workarea
  workareaEnd   : 0, //start index of section in the workarea
  selectorStart : 2, //index of first valid section
  
  reMatchSectionHeader : new RegExp( '^(!+.*?)$', 'm' ), //lines starting with !,!!,!!!
  reMatchSectionSingle : new RegExp( '^!','m' ),         //lines starting with !
  reMatchSectionGlobal : new RegExp( '^!','mg' ),        //lines starting with !
  reMatchPreSingle     : new RegExp( "\\{\\{\\{([\\s\\S]*?)\\}\\}\\}" ),
  reMatchPreGlobal     : new RegExp( "\\{\\{\\{([\\s\\S]*?)\\}\\}\\}", "g" ),
  reVariables          : new RegExp ( "\\[\\{SET\\s+(\\w+?)='?([^']+?)'?\\s*\\}\\]", "m" ),
  rePermissions        : new RegExp( "\\[\\{ALLOW\\s+(\\w+?)\\s+([^\\}]+?)\\s*\\}\\]", "m" ),
  
  SPACES : "\u00a0\u00a0", // indentation space in selector dropdown 
  METADATA         : 1,    // selectedIndex for metadata
  ALLSECTIONS      : 0,    // selectedIndex for all sections
  DEFAULTSELECTORS : 2,    // default size of <SELECT> in plain.jsp
  SELECTEDCOLOR    : null, // temporary storage when selecting a color from a colorpopup
  
  /*
   * initialize EditTools on pageload 
   */
  onPageLoad : function()
  {
    this.mainarea = $( this.MainareaID ); if( !this.mainarea ) return;
    this.textarea = this.mainarea;
    this.workarea = $( this.WorkareaID ); if( !this.workarea ) return;
    this.textarea = this.workarea;
    this.selector = $( this.SelectID );   if( !this.selector ) return;

    this.workarea.onchange = function() { EditTools.onWorkAreaChanged() };
    this.selector.onchange = function() { EditTools.onSelectorChanged() };

    /* move the selector to the Favorites bar */
    var fav = $("favorites");
    var uname = getElementsByClassName(fav,"username", true);
    fav.insertBefore(this.selector, uname.nextSibling );
    
    this.onSelectorLoad();
    
    var cursor = URL.parseParameters( 'section' ); 
    if( cursor ) 
    {
      cursor = this.selectorStart + parseInt( cursor ) -1;
      if( (cursor < 0) || (cursor >= this.selector.options.length) ) cursor = 0 ;
      this.selector.options[cursor].selected = true;  
    }

    this.onSelectorChanged();

    this.buildColorPopup( this.ColorID );
    this.buildColorPopup( this.BackgroundID );
  } ,
  
  /* 
   * UPDATE/RFEFRESH the section selector dropdown
   * This function is called at startup, and everytime the section textarea changes
   * Postcondition: the section dropdown contains following entries
   *   0. ( all )
   *   1. ( metadata )
   *   2. start-of-page (if applicable)
   *   3. text==<<header 1...n>> , value==<<start position in main textarea>>
   *   Eeach entry of the list contains the start-offset in the main textarea
   */  
  onSelectorLoad : function()
  {
    var mainarea = this.mainarea.value;  
    var oldIndex = this.selector.selectedIndex; //remember current cursor
    var cursor   = this.DEFAULTSELECTORS;
    
    /* mask all headers inside a {{{ ... }}} but keep length unchanged! */
    while( this.reMatchPreSingle.test( mainarea ) )
    {
      var s = RegExp.$1.replace( this.reMatchSectionGlobal, Wiki.DELIM );  //mask '!'  
      mainarea = mainarea.replace( this.reMatchPreSingle, Wiki.DELIM+'{{'+s+'}}}' );
    }

    var i;
    do
    {
      i = mainarea.search( this.reMatchSectionHeader );
      if( ( cursor == this.DEFAULTSELECTORS ) && ( i > 0 ) ) 
      { 
        var sop = "( start of page )".localized();
        this.selector.options[cursor++] = new Option(sop, 0, false, false );
        this.selectorStart++;
      }
      if( i >= 0 )
      {
        var t = RegExp.$1;
             if( t.indexOf("!!!") == 0 ) { t = t.substr(3).trim(); }
        else if( t.indexOf("!!" ) == 0 ) { t = this.SPACES + t.substr(2).trim(); }
        else if( t.indexOf("!"  ) == 0 ) { t = this.SPACES + this.SPACES + t.substr(1).trim(); }
        this.selector.options[cursor++] = new Option( t, i, false, false );
        mainarea = mainarea.replace( this.reMatchSectionSingle, Wiki.DELIM );  //mask '!'  
      }
    }
    while( i != -1 );

    this.selector.options.length = cursor; //remove remaining stuff 
    if( oldIndex < cursor ) this.selector.options[oldIndex].selected = true;  
  } ,

  /*
   * the USER clicks a new item from the section selector dropdown
   * copy a part of the main textarea to the section textarea
   */
  onSelectorChanged : function( )
  {
    var se    = this.selector;
    var i     = se.selectedIndex;

    if( i == this.METADATA )
    {
      if( !Element.visible( this.MetaID ) ) 
      { 
        Element.show( this.MetaID );
        Element.hide( this.workarea, this.ToolbarID );
        this.onMetaLoad();
      }
    }
    else
    { 
      this.workareaStart = (i == -1) ? 0 : parseInt( se.options[i].value ); //bugfix safari
      this.workareaEnd   = this.mainarea.value.length;
      if( (i >= this.DEFAULTSELECTORS) && (i+1 < se.options.length) ) 
      { 
        this.workareaEnd = Number( se.options[i+1].value ); 
      }
      this.workarea.value = this.mainarea.value.substring(this.workareaStart, this.workareaEnd);

      Element.hide( this.MetaID );
      Element.show( this.workarea, this.ToolbarID );
    }
  } ,
  
  /*
   * Changes in the section textarea: 
   * happens when 
   *  (i)  textarea is changed and deselected (click outside the textarea) 
   *  (ii) user clicks a toolbar-button
   *  
   * 1) copy section textarea at the right offset of the main textarea
   * 2) refresh the section dropdown 
   */
  onWorkAreaChanged : function( )
  {
    if( this.selector.selectedIndex == this.METADATA ) return;
 
    var s = this.workarea.value;
    // suggested by KieronWilkinson
    if( s.lastIndexOf("\n") + 1 != s.length ) this.workarea.value += "\n";
    
    s = this.mainarea.value;
    s = s.substring( 0, this.workareaStart) + this.workarea.value 
      + s.substring( this.workareaEnd ) ; 
    this.workareaEnd = this.workareaStart + this.workarea.value.length;
    this.mainarea.value = s;

    this.onSelectorLoad();  
  } ,
  

  /**
   ** METADATA editor for Page Variables and Page ACLs(permissions)
   **/
   
  /* 
   * Load metadata into editor
   * move all metadata to the start of the main textarea
   */
  onMetaLoad: function()
  {
    this.removeAll( this.PermissionID );
    this.removeAll( this.MetadataID );      

    var s = this.mainarea.value;
    var t = s;

    s = s.replace( this.reMatchPreGlobal, Wiki.DELIM ); //remove {{{...}}} 

    while( this.rePermissions.test( s ) )
    {
      this.addRow( this.PermissionID, null, RegExp.$2, RegExp.$1 );
      s = s.replace( this.rePermissions, "" );
    }
    this.ensureNotEmpty( this.PermissionID );    

    while( this.reVariables.test( s ) )
    {
      var name = RegExp.$1;
      this.addRow( this.MetadataID, "default-"+name, name, RegExp.$2 );
      s = s.replace( this.reVariables, "" );
    }
    this.ensureNotEmpty( this.MetadataID );    

    /* reinsert the removed {{{...}}} so we can put it back */
    while( this.reMatchPreGlobal.test( t ) )
    {
      s = s.replace( Wiki.DELIM, "{{{" + RegExp.$1 + "}}}" );
      t = t.replace( "{{{", "" );
    }

    this.mainarea.value = s.trim();
    this.metasize = 0;
    this.onPageChange();    
  } ,

  /* ensure the metadata tables are never empty */
  ensureNotEmpty: function( tableID )
  {
    t = $T( tableID ); 
    if( t && t.rows.length == 1 ) this.addRow( tableID ); 
  } ,
  
  /* remove all entries for a metadata table */
  removeAll: function( tableID )
  {
    var t = $T( tableID ); if( !t ) return;
    while( t.rows.length >= 2 ) t.deleteRow( 1 );
  } ,
  
  /* onclick handler to remove a table row */
  removeRow: function( node )
  {
    var row = getAncestorByTagName( node, "TR" ); if( !row ) return;
    var t = getAncestorByTagName( node, "TABLE" ); if( !t ) return;
    
    row.parentNode.removeChild( row );
    this.ensureNotEmpty( t ); 

    this.onPageChange();
  } ,

  /* add new metadata row
   * - tableID : target table to add row to
   * - defaultRowID : add a copy of this row; when null identify default yourself
   * - v1,v2 : optional - values
   */
  addRow: function( tableID, defaultRowID, v0, v1 )
  {
    var t = $( tableID ); if( !t ) return;
    var d = $( defaultRowID ); 
    if( !d ) d = $( "default-" + t.id ); 
    if( !d ) return;

    var r = d.cloneNode( true );
    r.id = null;
    $T(t).appendChild( r );

    if( v0 )  this.setValue( r.cells[0], v0 ); 
    if( v1 )  this.setValue( r.cells[1], v1 ); 
    this.onPageChange();
  } ,

  /* set value of first form element inside a table cell */
  setValue: function( node, value )
  {
    if( value == "" ) return;
    for( var e = node.firstChild; e; e = e.nextSibling )
    {
      if( e.nodeName =="INPUT" )
      { 
        e.value = value;
        return; 
      }
      else if( e.nodeName == "SELECT" )
      {
        for( var i=0; i<e.options.length; i++ )
        {
          if( value == e.options[i].value )
          {
            e.options[i].selected = true;
            return;
          }
        }
        return;
      }
    } 
  } ,
  
  /* get value of first form element inside a table cell */
  /* relies on $F of prototype.js */
  getValue: function( node )
  {
    for( var e = node.firstChild; e; e = e.nextSibling )
    {
      if( (e.nodeName == "INPUT") || (e.nodeName == "SELECT") )  
      {
        var v = $F(e);
        /* skip special defaultvalues with onfocus/onblur handlers */
        if( e.onfocus && (e.defaultValue==v) ) v = null;  
        return v;
      }
    } 
    return getNodeText( node );
  } ,


  // collect metadata wiki markup and prepend it to the mainarea
  onPageChange: function()
  {
    var s = [];

    var t = $T( EditTools.MetadataID );
    if( t )
    {
      for( var i=1; i < t.rows.length; i++ )
      {
        var r = t.rows[i];
        var n = this.getValue( r.cells[0] ); if( !n ) continue;
        var v = this.getValue( r.cells[1] ); if( !v ) continue;
        s.push( "[{SET " + n + "='" + v + "' }]\n" );
      }
    }
  
    t = $T( EditTools.PermissionID );
    if( t )
    {
      for( var i=1; i < t.rows.length; i++ )
      {
        var r = t.rows[i];
        var p = this.getValue( r.cells[1] ); if( !p ) continue;
        if( p == "(none)" ) continue;
        var v = this.getValue( r.cells[0] ); if( !v ) continue;
        s.push( "[{ALLOW " + p + " " + v + " }]\n" );
      }
    }
  
    var meta = s.join('');
    this.mainarea.value = meta + this.mainarea.value.substr(this.metasize);  // old metasize  
    this.metasize = meta.length;

    this.onSelectorLoad( );  
  } ,
  
  
  /**
   ** TOOLBAR stuff
   **/
  
  /* TOOLBAR: find&replace */
  doReplace: function( )
  {
    var findText    = $(this.FindTextID).value; if( findText == "") return;
    var replaceText = $(this.ReplaceTextID).value;
    var isRegExp    = $(this.RegExpID).checked;
    var reGlobal    = ( $(this.GlobalID).checked ? "g" : "" ) ;
    var reMatchCase = ( $(this.MatchCaseID).checked ? "" : "i") ;

    var sel = TextArea.getSelection( this.textarea );
    var data = ( !sel || (sel=="") ) ? this.textarea.value : sel;
  
    if( !isRegExp ) /* escape all special re characters */
    {
      var re = new RegExp( "([\.\*\\\?\+\[\^\$])", "gi");
      findText = findText.replace( re,"\\$1" );
    }
    
    var re = new RegExp( findText, reGlobal+reMatchCase+"m" ); //multiline
    if( !re.exec(data) )
    {
      alert( "No match found!".localized() );
      return(true);
    } 
      
    data = data.replace( re, replaceText );  
  
    this.storeTextarea();
    if( !sel || (sel=="") ) { this.textarea.value = data; }
    else                    { TextArea.setSelection( this.textarea, data ); }
    if( this.textarea.onchange ) this.textarea.onchange();
    
    return( true );
  } ,
    
  /* TOOLBAR: insert [select-text|page-link] */
  link: function( ) 
  {
    var s = TextArea.getSelection( this.textarea );

    var link = prompt("Enter \"pagename\" or \"linktext | pagename\" or \"http://...\"" , s );
    if( !link ) return;

    if( link.indexOf("|") != -1 )       { link = s + " [" + link + "]"; }
    else if( (s != "") && (link != s) ) { link = "[" + s + "|" + link + "]"; }
    else                                { link = "[" + link + "]"; }

    this.storeTextarea();
    TextArea.setSelection( this.textarea, link );
  } ,

  /* TOOLBAR: insert wiki markup block; avoid double /n/n at start */
  blockText : function( format )
  {
    var s = TextArea.getSelection( this.textarea );
    this.storeTextarea();

    if( !TextArea.isSelectionAtStartOfLine(this.textarea) ) 
    { 
      format = "\n" + format;
    }
    format = this.getSelectedColor( format ) ;
    s = format.replace( /\$/, s)
    TextArea.setSelection( this.textarea, s );
  } ,

  /* TOOLBAR: insert inline wiki markup, such as BOLD, ITALIC */
  inlineText : function( format )
  {
    var s = TextArea.getSelection( this.textarea ); 

    if( format.indexOf("$") == -1 )
    {
      format += "$";
    }
    else
    {
      if( !s || (s=="") ) return( alert("Please make first a selection.".localized()) );
    }    

    this.storeTextarea();
    format = this.getSelectedColor( format ) ;
    s = format.replace( /\$/, s)
    TextArea.setSelection( this.textarea, s );
  } ,

  /* TOOLBAR: Prefix a line of wikimarkup
   * -- if line already starts with prefix markup: remove it
   * -- if cursor not at begin of line: insert \n
   * -- make sure to insert space if required
   * -- repeat * or # if required
   */
  linePrefix : function( format )
  {
    this.storeTextarea();

    var s = TextArea.getSelection( this.textarea );

    if( ( format == "*" ) && (s.match( /^(\*+)/ )) )
    {
      format = format + RegExp.$1;
    }
    if( ( format == "#" ) && (s.match( /^(\#+)/ )) )
    {
      format = format + RegExp.$1;
    }
    s = s.replace( /^[!\*#]*\s*/, format + " "); 

    if( !TextArea.isSelectionAtStartOfLine( this.textarea ) ) 
    { 
      s = "\n" + s;
    }
    TextArea.setSelection( this.textarea, s );
  } ,
  
  /* TOOLBAR: cut/copy/paste clipboard functionality */
  CLIPBOARD : null,
  clipboard : function( format )
  {
    var s = TextArea.getSelection( this.textarea );
    if( !s || s == "") return;

    this.CLIPBOARD = s ;
    $( this.PasteID ).className = this.ToolbarMarker;
    var ss = format.replace( /\$/, s);
    if( s == ss ) return; //copy

    this.storeTextarea(); //cut
    TextArea.setSelection( this.textarea, ss );
  } ,
  paste : function()
  {
    if( !this.CLIPBOARD ) return;
    this.storeTextarea();
    TextArea.setSelection( this.textarea, this.CLIPBOARD );
  } ,

  /* UNDO functionality: use by all toolbar and find&replace functions */
  UNDOstack : [],
  REDOstack : [],
  UNDOdepth : 20,
  storeTextarea : function()
  {
    this.UNDOstack.push( this.mainarea.value );
    $( this.UndoID ).disabled = '';
    this.REDOstack = [];
    $( this.RedoID ).disabled = 'true';
    if( this.UNDOstack.length > this.UNDOdepth ) this.UNDOstack.shift();
  } ,

  undoTextarea : function( )
  {
    if( this.UNDOstack.length > 0 ) 
    {
      $( this.RedoID ).disabled = '';
      this.REDOstack.push( this.mainarea.value );
      this.mainarea.value = this.UNDOstack.pop();
    }
    if( this.UNDOstack.length == 0 ) $( this.UndoID ).disabled = 'true';
    if( !this.selector ) return;
    this.onSelectorLoad();
    this.onSelectorChanged();
    this.textarea.focus();
  } , 
  redoTextarea : function( )
  {
    if( this.REDOstack.length > 0 ) 
    {
      $( this.UndoID ).disabled = '';
      this.UNDOstack.push( this.mainarea.value );
      this.mainarea.value = this.REDOstack.pop();
    }
    if( this.REDOstack.length == 0 ) $( this.RedoID ).disabled = 'true';
    if( !this.selector ) return;
    this.onSelectorLoad();
    this.onSelectorChanged();
    this.textarea.focus();
  } ,
  
  /* TOOLBAR: foreground and background color functionality */ 
  ColorPopup: [["white", "fcc", "fc9", "ff9", "ffc", "9f9", "9ff", "cff", "ccf", "fcf"],
               ["ccc", "f66", "f96", "ff6", "ff3", "6f9", "3ff", "6ff", "99f", "f9f"],
               ["silver", "red", "f90", "fc6", "yellow", "3f3", "6cc", "3cf", "66c", "c6c"],
               ["999", "c00", "f60", "fc3", "fc0", "3c0", "0cc", "36f", "63f", "c3c"],
               ["666", "900", "c60", "c93", "990", "090", "399", "33f", "60c", "939"],
               ["333", "600", "930", "963", "660", "060", "366", "009", "339", "636"],
               ["black", "300", "630", "633", "330", "030", "033", "006", "309", "303"] ,
               ["maroon", "purple", "fuchsia", "green", "lime", "olive", "navy", "blue", "teal", "aqua"]] ,
  buildColorPopup: function( id )
  {
    var eID = id + "Popup";
    var e = $( eID ); if( !e ) return;
    
    var span = document.createElement( "span");
    span.innerHTML = ": select a color";
    e.appendChild( span );
    
    var table = document.createElement( "table" );
    var tbody = document.createElement( "tbody" ); 
    table.appendChild( tbody );
    
    var colors = this.ColorPopup;
    for (var i = 0; i < colors.length; i++) 
    {
      var tr = document.createElement( "tr" );
      for (var j = 0; j < colors[i].length; j++) 
      {
        var c = Colors.asHTML( colors[i][j] );
        var td = document.createElement( "td" );
        td.onmouseout = function( e )  { span.innerHTML = ": select a color"; this.style.borderColor = "white"; }
        td.onmouseover = function( e ) { span.innerHTML = ": "+Colors.asHTML(this.style.backgroundColor); this.style.borderColor = "gray"; }

        td.onmousedown = function( e ) { EditTools.clickColorPopup( this, eID ); } ;
        td.innerHTML = "&nbsp;";
        td.style.backgroundColor = c; 
        tr.appendChild(td);
      }
      tbody.appendChild( tr );
    }
    e.appendChild( table );
    if( e.onclick) { e.clickChain = e.onclick;  e.onclick = function() {}; }
  } ,
  
  clickColorPopup : function( td, popup )
  {
    EditTools.SELECTEDCOLOR = Colors.asHTML( td.style.backgroundColor );
    var e = $( popup ); if( !e ) return;
    if( e.clickChain ) e.clickChain();
    Element.hide( e );
  } ,
  
  getSelectedColor : function( s )
  {
    if( this.SELECTEDCOLOR ) s = s.replace( /\$\$/, this.SELECTEDCOLOR ) ;
    this.SELECTEDCOLOR = null;
    return s;
  }
      
} 


/** TextArea support routines
 ** allowing to get and set the selected texted in a textarea
 ** These routines have browser specific code to support IE
 ** Also runs on Safari.
 **
 ** Inspired by JS QuickTags from http://www.alexking.org/
 ** but extended for JSPWiki -- DirkFrederickx Jun 06.
 **/
var TextArea =
{
  getSelection: function(id)
  {
    var f = $(id); 
    if( !f ) return ""; 
    
    if( document.selection )  //IE support
    {
      f.focus();
      return( document.selection.createRange().text );
    }
    else if( f.selectionStart 
          || f.selectionStart == '0')  //MOZILLA/NETSCAPE support
    {
      f.focus();   
      var start= f.selectionStart;
      var end  = f.selectionEnd;
      return( f.value.substr( start, end-start ) );
     } 
    else 
    {
      return( "" );
    }    
  } ,
  
  /* replaces the selection with aValue, and returns with aValue selected */
  setSelection: function(id, aValue)
  {
    var f = $(id); 
    if( !f ) return ""; 
    f.focus();
     
    if( document.selection )  //IE support
    {
      document.selection.createRange().text = aValue;
      f.focus();
    }
    else if( f.selectionStart 
          || f.selectionStart == '0')  //MOZILLA/NETSCAPE support
    {
      f.focus();   
      var start= f.selectionStart;
      var end  = f.selectionEnd;
      var top  = f.scrollTop;
      
      f.value  = f.value.substring( 0, start )
               + aValue 
               + f.value.substring( end, f.value.length );
      f.focus();
      f.selectionStart = start;
      f.selectionEnd = start + aValue.length;
      f.scrollTop = top;
    } 
    else 
    {
      f.value += value;
      f.focus();
    }
    if( f.onchange ) f.onchange();
  } ,
  
  /* check whether selection is preceeded by a \n (peek-ahead trick) */
  isSelectionAtStartOfLine : function( id )
  {
    var f = $(id); 
    if( !f ) return ""; 
    f.focus();

    if( document.selection )  //IE support
    {
      var r1 = document.selection.createRange();
      var r2 = document.selection.createRange();
      r2.moveStart( "character", -1);
      if( r1.compareEndPoints("StartToStart", r2) == 0 ) return true;
      if( r2.text.charAt(0).match( /[\n\r]/ ) ) return true;
    }
    else if( f.selectionStart 
          || f.selectionStart == '0')  //MOZILLA/NETSCAPE support
    {
      if( f.selectionStart == 0) return true;
      if( f.value.charAt(f.selectionStart-1) == "\n" ) return true;
    } ; 
    return false;
  }  
}


/**
 ** OnLoad : run following stuff after the page was loaded
 **/
Event.observe( 
  window, 
  "load", 
  function()
  {
    EditTools.onPageLoad(); //edit only
  } );
