/*

  Replaced original JSON with JSON.stringify 
  object.prototype and array.prototype break other code

*/

var _AjaxMessageCenter = new AjaxMessageCenter();
var _areBundlingMessagesIntoOneRequest = false;
var xhrObjects = new Array();
var xhrObjectsInUse = new Array();

function Ajax(url,options) {

  if(options.callback)
    sendAjaxMessage({targetClass:'__direct',
                    targetMethod: url,
                    targetMethodArguments: options.parameters,
                    callback: options.callback,
                    options: options});
  else
    sendAjaxMessage({targetClass:'__direct',
                    targetMethod: url,
                    targetMethodArguments: options.parameters,
                    options: options});
}

/**
 * sendAjaxMessage()
 * 
 * Send a single ajax message to the server or append to the queue if called
 * between beginAddingAjaxMessagesToQueue()/sendQueuedAjaxMessages().
 *
 * Arguments can be specified in 2 ways
 * 
 * 1.
 * @param targetClass string
 * @param targetMethod string
 * @param targetMethodArguments anything (optional)
 * @param callbackFunction function (optional)
 * @param busyIndicator bool (optional) default : false
 *
 * 2.
 * @param options object {targetClass:'<string>',
 *                        targetMethod:'<string>',
 *                        targetMethodArguments:<mixed>,
 *                        callback:<function>,
 *                        busyIndicator:<bool>,
 *                        asynchronous:<bool>}
 */
function sendAjaxMessage(targetClass, targetMethod, targetMethodArguments, callbackFunction)
{
//  console.info('sendAjaxMessage', targetClass, targetMethod, targetMethodArguments, callbackFunction);
   
  if(typeof(targetClass) == 'string') {
    message = new AjaxMessage(targetClass, targetMethod, targetMethodArguments, callbackFunction);
    
    _AjaxMessageCenter.appendMessageToQueue(message);
    
    _AjaxMessageCenter.busyIndicator = arguments.length>4 ? arguments[4] : false;
  } else {
    var options = targetClass;
    options.callback = typeof options.callback == 'function' ? options.callback : false;
    options.callback = typeof options.callback == 'object' ? options.callback : false;
    
    _AjaxMessageCenter.busyIndicator = options.busyIndicator == undefined ? false:options.busyIndicator;
    _AjaxMessageCenter.asynchronous = options.asynchronous == undefined ? true:options.asynchronous;
    
    message = new AjaxMessage(options.targetClass, options.targetMethod, options.targetMethodArguments, options.callbackFunction,options.options);
    _AjaxMessageCenter.appendMessageToQueue(message);
  }
  if (!_areBundlingMessagesIntoOneRequest) {
    _AjaxMessageCenter.sendQueuedMessages();
  }
  
}

/**
 * beginAddingAjaxMessagesToQueue()
 * 
 * After this function has been called, any sendAjaxMessage() calls will be added
 * to a queue and sent when you call sendQueuedAjaxMessages(). Using the queue
 * will bundle multiple messages into a single HTTP request, which is faster and
 * more reliable.
 */
function beginAddingAjaxMessagesToQueue()
{
  if (_areBundlingMessagesIntoOneRequest) {
    //alert('WARNING: Cannot nest begin/endSendingAjaxMessages().');
    return;
  }
  
  _areBundlingMessagesIntoOneRequest = true;
}

/**
 * sendQueuedAjaxMessages()
 * 
 * Send any messages in the queue
 */
function sendQueuedAjaxMessages()
{
  if (!_areBundlingMessagesIntoOneRequest) {
    //alert('WARNING: endSendingAjaxMessages() called when we are not sending messages.');
    return;
  }
  
  _areBundlingMessagesIntoOneRequest = false;
  
  _AjaxMessageCenter.sendQueuedMessages();
}




function AjaxMessageCenter()
{
  this.messageQueue = new Array();
  this.busyIndicatorRetainCount = 0;
  this.busyIndicator = true;
  this.asynchronous = true;
  this.appendMessageToQueue = function(message)
  {
    this.messageQueue.push(message);
  }
  
  this.sendQueuedMessages = function()
  {
    messageObjects = new Array();
    for (var i = 0; i < this.messageQueue.length; i++) {
      messageObjects.push(this.messageQueue[i].buildMessageObj());
    }

//    console.info('ajax sendQueuedMessages - ' + messageObjects[0].targetClass + '.' + messageObjects[0].targetMethod);
  
    var req = this.getXHR();
    var thisVar = this;
    var messageQueueCopy = thisVar.messageQueue;

    if(messageObjects.length==1 && messageObjects[0].targetClass=='__direct') {
//      console.log(messageObjects[0].targetMethod);
      if(messageObjects[0].targetMethod.indexOf('http://')==0 || messageObjects[0].targetMethod.indexOf('https://')==0) {
        try {
          req.open('POST', messageObjects[0].targetMethod, this.asynchronous);
        } catch (e) {
          //console.log(e);
        }
        req.onreadystatechange = function() { thisVar.processXHRStatusChange(req, messageQueueCopy); };
        
        if(messageObjects[0].targetMethodArguments) {
          var boundaryString = BASE_URL.replace(/[^a-z]/gi, '');
          var boundary = '--' + boundaryString;
          req.setRequestHeader("Content-type", "application/x-www-form-urlencoded;");
          req.setRequestHeader("Connection", "close");
          requestbody = messageObjects[0].targetMethodArguments;
          req.setRequestHeader("Content-length", requestbody.length);
          req.send(requestbody);
        } else {
          req.send('');
        }
        // set onreadystatechange after open() to allow reuse of req in IE 
        
      } else {
        try {
          req.open('POST', messageObjects[0].targetMethod, this.asynchronous);
        } catch(e) {
          // console.log(e);
        }
      }
    } else {

      try {
        req.open('POST', JPSPAN_BASE_URL+'includes/postoffice.php', this.asynchronous);
      } catch(e) {
        req.open('POST', '/includes/postoffice.php', this.asynchronous);
      }

      // set onreadystatechange after open() to allow reuse of req in IE 
      req.onreadystatechange = function() { thisVar.processXHRStatusChange(req, messageQueueCopy); };
      
      req.send(JSON.stringify(messageObjects));
    }
    
    // FF doesn't call the onreadystatechange function for syncnronous calls, so call it manually
    // (apparently it *did* if Firebug < 1.1 was installed)  
    if (!this.asynchronous && checkGeko())
      thisVar.processXHRStatusChange(req, messageQueueCopy);
    
    this.messageQueue = new Array();
    if(this.busyIndicator==true) {
      this.showBusyIndicator();
    }
  }
  
  /**
   * Returns an XHR object.
   * 
   * A new object is created only if no idle ones available.
   * 
   * @return {XMLHttpRequest}
   */
  this.getXHR = function()
  {
    // find existing idle object to reuse, if any
    for(var i in xhrObjects)
    {
      var xhr = xhrObjects[i];
      if (!xhrObjectsInUse[i])
      {
//        console.info('reusing XHR object');
        xhrObjectsInUse[i] = true;
        return xhr;
      }
    }
    
    // create new object
//    console.info('creating new XHR object');
    var xhr = this.buildXHR();
    xhrObjects.push(xhr);
    xhrObjectsInUse.push(true);
    return xhr;
  }
  
  /**
   * Creates a new XHR object.
   * 
   * @return {XMLHttpRequest}
   */
  this.buildXHR = function()
  {
    var req = false;

    try { req = new XMLHttpRequest(); } catch(e) {} // most browsers
    if (!req) try { req = new ActiveXObject('Msxml2.XMLHTTP'); } catch(e) {} // ie6
    if (!req) try { req = new ActiveXObject('Microsoft.XMLHTTP'); } catch(e) {} // ie5
    if (!req) {
      //alert('Error: Your browser does not support AJAX. Please upgrade your browser and try again.'); // unsupported browser
      return null;
    }

    return req;
  }
  
  /**
   * Marks an XHR object as available for reuse
   * @param {XMLHttpRequest} xhr
   */
  this.recycleXHR = function(xhr)
  {
    for(var i=0; i<xhrObjects.length; ++i)
      if (xhrObjects[i] == xhr)
         xhrObjectsInUse[i] = false;
  }
  
  this.processXHRStatusChange = function(xhrObject, messageObjects)
  {
    // make sure we're finnished loading.
    if (xhrObject.readyState < 4)
      return;
    

 	 	// check for http error of "0" - firefox does this
  	if (xhrObject.status == 0)
  	  return;
    
    // Make we didn't 404 or anything like that. NOTE: FireFox occasionally logs a wierd exception when executing 'xhrObject.status'.
    if (xhrObject.status != 200) {
      //alert('Error communicating with server.' + "\n\n HTTP Error: " + xhrObject.status);
      console.error('Error communicating with server.' + "\n\n HTTP Error: " + xhrObject.status);
      if(this.busyIndicator==true) {
        this.hideBusyIndicator();
      }
      
      this.recycleXHR(xhrObject);
      return;
    }
    
    if(messageObjects.length==1 && messageObjects[0].targetClass=='__direct') {
      if(messageObjects[0].callbackFunction) {
        messageObjects[0].callbackFunction(xhrObject.responseText);
        return true;
      }
      if(messageObjects[0].options.update) {
        messageObjects[0].options.update.setValue(xhrObject.responseText);
        return true;
      }
      return false;
    }
    
//    console.info('ajax processXHRStatusChange - ' + messageObjects[0].targetClass + '.' + messageObjects[0].targetMethod);
          
    // Parse response.
    results = xhrObject.responseText.parseJSON();
//    console.log('ajax results', results);
    
//		console.log(xhrObject.responseText);
    if (results == false) {
      //alert('Error communicating with server.' + "\n\nServer response was: \n" + xhrObject.responseText);
      if(this.busyIndicator==true) {
        this.hideBusyIndicator();
      }
      
      this.recycleXHR(xhrObject);
      return;
    }
  
    // Run callbacks.
    for (var i = 0; i < messageObjects.length; i++) {
      if (messageObjects[i].callbackFunction != null) {
        try
        {
          //console.log('ajax callback:', messageObjects[i].callbackFunction);
          messageObjects[i].callbackFunction(results[i]);
        }
        catch(ex)
        {
          //console.log('ajax', ex);
          throw(ex);  
        }
      } else {
        for(var j in results[i]) {
          try
          {
            var s = j+'(results[i][j])';
            //console.log('eval: ', s);
            eval(s);
          }
          catch(ex)
          {
            //console.error('ajax', ex); 
            throw(ex);  
          }
        }
      }        
    }
    
    this.hideBusyIndicator();
    this.recycleXHR(xhrObject);
  }
  
  this.showBusyIndicator = function ()
  {
    this.busyIndicatorRetainCount++;
    
    busyEl = document.getElementById('ajaxmessage-busy');
    if (busyEl != null) busyEl.style.display = (this.busyIndicatorRetainCount > 0) ? 'block' : 'none';
  }
  
  this.hideBusyIndicator = function ()
  {
    this.busyIndicatorRetainCount--;
    
    busyEl = document.getElementById('ajaxmessage-busy');
    if (busyEl != null) busyEl.style.display = (this.busyIndicatorRetainCount > 0) ? 'block' : 'none';
  }
    
}

function AjaxMessage(targetClass, targetMethod, targetMethodArguments, callbackFunction, options)
{
  this.targetClass = targetClass;
  this.targetMethod = targetMethod;
  this.targetMethodArguments = targetMethodArguments;
  this.callbackFunction = callbackFunction;
  this.options = options;

  this.buildMessageObj = function()
  {
    var dataObj = {
      targetClass: this.targetClass,
      targetMethod: this.targetMethod
    }
    
    if (this.targetMethodArguments != null)
      dataObj.targetMethodArguments = this.targetMethodArguments;
    
    if (this.callbackFunction != null)
      dataObj.callbackFunction = this.callbackFunction.toString();
    
    if(this.options != null)
      dataObj.options = this.options;
    
    return dataObj;
  }
}

var JSON;

(function() {
  
  // JSON library
  JSON = {
    stringify: function (arg) {
      switch (typeof arg) {
        case 'string'   : return '"' + encodeString(arg) + '"'; // break command is redundant here and below.
        case 'number'   : return String(arg);
        case 'object'   : 
          if (arg) {
            var out = [];
            if (arg instanceof Array) {
              for (var i = 0; i < arg.length; i++) {
                var json = this.stringify(arg[i]);
                if (json != null) out[out.length] = json;
              }
              return '[' + out.join(',') + ']';
            } else {
              for (var p in arg) {
                var json = this.stringify(arg[p]);
                if (json != null) out[out.length] = '"' + encodeString(p) + '":' + json;
              }
              return '{' + out.join(',') + '}';
            }
          }
          return 'null'; // if execution reaches here, arg is null.
        case 'boolean'  : return String(arg);
        // cases function & undefined return null implicitly.
      }
    },
    parse: function (text) {
      try {
        return !(/[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/.test(text.replace(/"(\\.|[^"\\])*"/g, '')))
          && eval('(' + text + ')');
      } catch (e) {
        return false;
      }
    }
  };

  // Test for modern browser (any except IE5).
  var JS13 = ('1'.replace(/1/, function() { return ''; }) == '');

  // CHARS array stores special strings for encodeString() function.
  var CHARS = {
    '\b': '\\b',
    '\t': '\\t',
    '\n': '\\n',
    '\f': '\\f',
    '\r': '\\r',
    '\\': '\\\\',
    '"' : '\\"'
  };
  for (var i = 0; i < 32; i++) {
    var c = String.fromCharCode(i);
    if (!CHARS[c]) CHARS[c] = ((i < 16) ? '\\u000' : '\\u00') + i.toString(16);
  }
  
  function encodeString(str) {
    if (!/[\x00-\x1f\\"]/.test(str)) {
      return str;
    } else if (JS13) {
      return str.replace(/([\x00-\x1f\\"])/g, function($0, $1) {
        return CHARS[$1];
      });
    } else {
      var out = new Array(str.length);
      for (var i = 0; i < str.length; i++) {
        var c = str.charAt(i);
        out[i] = CHARS[c] || c;
      }
      return out.join('');
    }
  }
  
})();


String.prototype.parseJSON = function () {
    try {
        return (/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(this)) &&
            eval('(' + this + ')');
    } catch (e) {
      return false;
    }
};
