/**
* Utility functions
**** Copyright (c) 2006, Lindsey Simon <lsimon@commoner.com>
* All rights reserved.
* 
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
* 
* *       Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* *       Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/


/**
* So that console.log functions won't blow up IE
* for firebug extension
*/

if ( typeof console == "undefined" || typeof console.debug == "undefined" ) { 
   console = {}; 
   console.debug = console.log = console.info = console.warn = console.error = Prototype.emptyFunction;
   

   console.debug = console.info = function( msg ) {
      if ( !$( 'debugConsole' ) ) {
         var d = document.createElement( 'textarea' );
         d.setAttribute( 'id', 'debugConsole' );
         d.setAttribute( 'rows', 5 );
         d.setAttribute( 'cols', 60 );
         d.setAttribute( 'style', 'margin-top: 10px; font-size: 10px;' );
         d.value = "Initializing Debug...";
         document.body.appendChild( d );
      }
      $( 'debugConsole' ).value = $( 'debugConsole' ).value + "\n" + msg;
   }

}


// instantiate a global Utility class so we can write functions off of it
Utility = {};

// used in Ajax functions
Utility.UrlLastPiece = "";
/*
if ( window.location.pathname.match( /\w+\.\w{3,4}$/ ) )
   Utility.UrlLastPiece = window.location.pathname.match( /\w+\.\w{3,4}$/ )[0];
*/

// add a few functions onto String
Object.extend(String.prototype, {
      
   trim: function() {
      var s = this;
      while ( s.substring(0,1) == ' ' ) {
         s = s.substring( 1, s.length );
      }
      while ( s.substring( s.length - 1, s.length ) == ' ' ) {
         s = s.substring( 0, s.length - 1 );
      }
      return s;
   },
   
   // our friend from PHP
   addslashes: function() {
      return this.replace( /\'/g, "\\'");
   }
   
});

// add a few functions onto Array
Object.extend( Array.prototype, {
      
   unique: function( b ) {
      var a = [], i, l = this.length;
      for( i=0; i<l; i++ ) {
         if( a.indexOf( this[i], 0, b ) < 0 ) { a.push( this[i] ); }
      }
      return a;
   }
   
});
        
/**
* tagNamesToLowerCase
* converts any tagNames in a string to lowercase
* @param {string} xhtml
* @return xhtml lowercased
* @type {string}
*/
Utility.tagNamesToLowerCase = function( xhtml, replaceArray ) {
   
   if ( parts = xhtml.match( /<([A-Z]+)/g ) ) {
      parts = parts.unique();
      for (var i = 0, n=parts.length; i < n; i++) {
         // strip off the start tag caret
         var part = parts[i].substring(1);
         
         xhtml = xhtml.replace(new RegExp( '<' + part, 'g'), '<' + part.toLowerCase() );
         xhtml = xhtml.replace(new RegExp( '<\/' + part, 'g'), '<\/' + part.toLowerCase() );
      }
   }
   return xhtml;
}

/**
* serializeInnerToString
* depends on Sarissa library from cross browser compatibililty
* @param {object} DOM node
* @param {array} additional text strings to strip out
* @returns xhtml aka serialized innerHTML, with tagNames lower cased
* @type {string}
*/
Utility.serializeInnerToString = function( DOMNode, replaceArray ) {

   var xhtml = new XMLSerializer().serializeToString( DOMNode );
   var rootTagName = DOMNode.tagName;
   
   // now replace start and end tags
   var startTagRegexp = new RegExp( '<\/?' + rootTagName + '[^>]*?>', 'g' );
   xhtml = xhtml.replace( startTagRegexp, '' );
   
   return xhtml;
}

/**
* For some reason prototype doesn't have this
* probably because it's unreliable and/or gheigh
*/
Object.extend(Event, {	
   isRightClick: function(event) {
      return (((event.which) && (event.which == 3)) || ((event.button) && (event.button == 2)));
   }
});


/**
* Since onSuccess called before onComplete, we can test for errors here
* @param {obj} transport
* @param {obj} json
*/
Utility.AjaxOnSuccess = function( transport, json ) {

   console.debug("Utility.AjaxOnSuccess transport:" + transport + ", json:" + json);
   
   // test for PHP Fatal Error and Warning
   // #TODO lowercase and test the response text
   if ( transport.responseText.match( '<b>Notice</b>:' ) || transport.responseText.match( '<b>Warning</b>:' ) || transport.responseText.match( '<b>Fatal error</b>:' ) || transport.responseText.match( '<b>Parse Error</b>:' ) ) {
      throw { error_message:"PHP error, either Notice, Warning, or Fatal", error_details: transport.responseText, error_backtrace: null };
   }
   
   // test JSON for FineTooth FWExceptions
   if ( json && !json.success ) {
      // if we have an error_code or error_id from FWLoad, then json is an exception object
      if ( json.error_code  || json.error_id ) {
         throw json;
      }
   }
   
   // otherwise things are aok
   return true;     
}

/**
* onFailure generic Ajax problem
* @param {obj} transport
* @param {obj} json
*/
Utility.AjaxOnFailure = function( transport, json) {
   throw "Utility.AjaxOnFailure"; 
}

/**
* onException Ajax
* @param {obj} Ajax.Request
* @param {obj} exception from prototype
*/
Utility.AjaxOnExceptionCalledAlready = false;
Utility.AjaxOnException = function( request, exception ) {

   console.debug( "Utility.AjaxOnException exception:" + exception + ", error_message: " + exception.error_message );
   //return;
   
   // stop exception from overwriting another loading href
   if ( this.onExceptionCalledAlready ) return;
   
   // prevent strung together js exceptions from overriding this one
   this.onExceptionCalledAlready = true;
   
   
   // fix for a FF bug that throws an exception if the listener is gone away && exception.name.match( /80040111/ )
   if ( exception && exception.message && exception.message.match( /80040111/ ) )
      return;
   
   
   var error_code = exception.error_code ? exception.error_code : "2033";
   var error_message = exception.error_message ? escape(exception.error_message) : escape( "Exception thrown during Ajax transaction" );
   
   // maybe we have details?
   // or else in IE we don't have a lot
   // in FF we get lineNumber and fileName
   var error_details = exception.error_details ? exception.error_details.truncate( 500 ) : exception.description ? exception.description : exception.message +", on line " + exception.lineNumber + " in file " + exception.fileName;
   var error_backtrace = exception.error_backtrace ? exception.error_backtrace : escape( "Parameters: " + request.options.parameters.truncate( 500 ) + '\nMethod: ' + request.options.method + '\nFFStack:' + exception.stack );
   var error_referrer = exception.error_referrer ? exception.error_referrer : "index.php";
   
   // error form
   alert("Exception: " + error_details  );
}

/**
* Simle AJAX requests for json success using prototype
* param {obj} obj
*/
Utility.AjaxRequest = function( obj ) {
   console.debug( "Utility.AjaxRequest parameters: " + obj.parameters );
   
   
   // we add on a parameter to break cache and so we know we're dealing with AJAX on the serverside
   var random_num = Math.round( ( Math.random() * 666 ) );
   
   // method
   var method = obj.method ? obj.method : "get";
   
   // if not specified url, then use our current url
   var url = obj.url ? obj.url : Utility.UrlLastPiece
   
   new Ajax.Request( url, {
      asynchronous: obj.asynchronous ? obj.asynchronous : true,
      method: method,
      parameters: obj.parameters + "&AJAX=Ajax.Request_"+random_num,
      onLoading: obj.onLoading ? obj.onLoading : Prototype.emptyFunction,
      onSuccess: Utility.AjaxOnSuccess,
      onComplete: obj.onComplete,
      onFailure: Utility.AjaxOnFailure,
      onException: Utility.AjaxOnException
   });
}

/**
* Ajax updater stuffs innerHTML into a div with server returned xhtml 
* param {element_id} element_id to be updated
* param {obj} obj
*/
Utility.AjaxUpdater = function( element_id, obj ) {
   console.debug( "Utility.AjaxUpdater element_id:" + element_id + ", parameters: " + obj.parameters );
   
   // we add on a parameter to both break cache and so we know we're dealing with AJAX serverside
   var random_num = Math.round( ( Math.random() * 666 ) );
   
   // method
   var method = obj.method ? obj.method : "get";
   
   // if not specified url, then use our current url
   var url = obj.url ? obj.url : Utility.UrlLastPiece;
   
   // do it
   new Ajax.Updater( element_id, url, {
      asynchronous: obj.asynchronous ? obj.asynchronous : true,
      method: method,
      parameters: obj.parameters + "&AJAX=Ajax.Updater_"+random_num,
      onLoading: obj.onLoading ? obj.onLoading : Prototype.emptyFunction,
      onSuccess: Utility.AjaxOnSuccess,
      onComplete: obj.onComplete,
      onFailure: Utility.AjaxOnFailure,
      onException: Utility.AjaxOnException,
      evalScripts: true
   });
}

/**
* Utility.tick
* @param {string} caller function
*/
Utility._timer = [];
Utility.tick = function( caller ) {
   Utility._timer[caller] = {};
   Utility._timer[caller].start = new Date();
}

/**
* Utility.tock
*/
Utility.tock = function( caller ) {
   Utility._timer[caller].end = new Date();
   console.info( caller + ' took: '  + ( Utility._timer[caller].end  - Utility._timer[caller].start ) + ' ms.' );   
}

/**
* Add on selectNodes for XMLDocuments
* see http://blog.km0ti0n.be/js/MozXPath/
*/
if( document.implementation.hasFeature("XPath", "3.0") && !XMLDocument.prototype.selectNodes ) { 
   // prototying the XMLDocument 
   XMLDocument.prototype.selectNodes = function(cXPathString, xNode) 
   { 
      if( !xNode ) { xNode = this; }
      var oNSResolver = this.createNSResolver(this.documentElement) 
      var aItems = this.evaluate(cXPathString, xNode, oNSResolver,
         XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null) 
      var aResult = [];
      for( var i = 0; i < aItems.snapshotLength; i++) 
      { 
         aResult[i] = aItems.snapshotItem(i);
      } 
      return aResult;
   }
   // prototying the XMLDocument 
   XMLDocument.prototype.selectSingleNode = function(cXPathString, xNode) 
   { 
      if( !xNode ) { xNode = this; }
      var xItems = this.selectNodes(cXPathString, xNode);
      if( xItems.length > 0 ) 
      { 
         return xItems[0];
      } 
      else 
      { 
         return null;
      } 
   }
}



