/**
 * Copyright (c) 2009
 * TechEmpower, Inc.
 * All Rights Reserved.
 *
 * ajaxconnector.js
 *
 * Development history:
 *   Jan 16, 2009 - jdavis - Created
 */

/**
 * A lightweight ajax connector factory that lets you generate connectors to 
 * send ajax requests. A nice alternative to yahoo.
 *
 * A global connectorFactory variable will be intialized and available after this script
 * is included.
 */

//
// Global elements
//

//
// Connection Stats Constants
//
var CONNECTION_STATE_IDLE = -2;
var CONNECTION_STATE_LOCKED = -1;

//
// Global list of open connectors
//
var openAjaxConnectors = new Array();

//
// Master callback function used to dispatch to the actual callbacks on the
// open ajax connectors.
//
function notifyAjaxConnectors()
{
  var connectors = new Array();
  
  for (i in openAjaxConnectors)
  {
    // if a connector's callback returns true, then it has completed and should 
    // be dropped from the open connector list.
    if (openAjaxConnectors[i].callBack() == false)
    {
      connectors.push(openAjaxConnectors[i]);
    }
  }
  
  openAjaxConnectors = connectors;
}

//
// A dummy function that is assigned to the onReadyStateChanged function in 
// order to prepare the connector for reuse.
//
function resetAjaxDummy() {}

//
// Objects
//

/**
 * AjaxResponse
 *
 * An AjaxResponse Object will be used to contain the results of the ajax call. 
 * Contains the response in both text and xml form along with the status code 
 * returned and whatever message goes along with it.
 */
function AjaxResponse(xmlHTTP)
{
  this.responseText = xmlHTTP.responseText;
  this.responseXML = xmlHTTP.responseXML;
  this.status = xmlHTTP.status;
  this.statusText = xmlHTTP.statusText;
  
  this.wasSuccessful = function() { return this.status == 200; }
}


/**
 * AjaxConnector
 *
 * An AjaxConnector Object manages the state of the ajax call. 
 * The constructor takes in the following parameters. All can be defined later.
 *
 * url - The url to connect to
 * callback - The callback function that will be called once the connection completes.
 *            An AjaxResponse object will be passed to this function as a parameter.
 * invoker - An optional Object to be considered the "invoker" of the callback. We be 
 *           Passed along as a second parameter to the callback if specified. Can be 
 *           thought of as a "this" to use in the context of the callback.
 */
function AjaxConnector(url, callback, invoker)
{
  if (url == undefined)
    this.url = null;
  else
    this.url = url;
  
  if (callback == undefined)
    this.responseCallback = function() {};
  else
    this.responseCallback = callback;
    
  if (invoker != undefined)
    this.invoker = invoker;

  this.connectionState = CONNECTION_STATE_IDLE;
  
  //
  // Releases the connector so it can be reused.
  //
  this.release = function()
  {
    this.connectionState = CONNECTION_STATE_IDLE;
  }
  
  //
  // Performs the actual connection and initiates the call.
  // If post is true then a POST request will be made rather than a GET.
  // Parameters is a url-encoded string of parameters to send in the request.
  // If wait is true then SJAX will be used. That means it will wait for a 
  // response before returning.
  // Returns false if there was a problem.
  //
  this.connect = function(post, parameters, wait)
  {
    if (this.url == undefined || this.url == null || this.xmlHTTP == null)
      return false;
    this.logMessage("Connecting to: " + this.url + ", with params: " + parameters);
    if (wait == undefined)
      wait = false;
    
    // If SJAXing it up then there is no need to add it to the open connectors list.
    if (!wait)
      openAjaxConnectors.push(this);
    
    try
    {
      if( post )
      {
        // This is a POST so prepare the request
        this.xmlHTTP.open("POST", this.url, !wait);

        if (wait)
          this.xmlHTTP.onreadystatechange = resetAjaxDummy;
        else
          this.xmlHTTP.onreadystatechange = notifyAjaxConnectors;

        this.xmlHTTP.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        this.xmlHTTP.setRequestHeader("Content-length", parameters.length);
        this.xmlHTTP.setRequestHeader("Connection", "close");
        this.xmlHTTP.send(parameters);
      }
      else
      {
        // This is a GET so append the parameters to the url. An additional random parameter 
        // is included to make sure the response is not cached.
        if (parameters.length > 0)
          parameters += "&";
        this.xmlHTTP.open("GET", this.url + "?" + parameters + "r=" + Math.random(), !wait);

        if (wait)
          this.xmlHTTP.onreadystatechange = resetAjaxDummy;
        else
          this.xmlHTTP.onreadystatechange = notifyAjaxConnectors;

        this.xmlHTTP.send(null);
      }
    }
    catch(e)
    {
      // Uh oh
      this.logMessage("Exception! " + e);
      return false;
    }
    
    // make sure that the callback gets called at least once in case firefox never called it
    if (wait)
    {
      this.callBack();
    }
    
    return true;
  }
  
  //
  // Creates the XMLHttpRequest Object for this connector.
  // Will construct the appropriate one for the browser.
  //
  this.createRequest = function()
  {
    try
    {
      // Firefox, Opera 8.0+, Safari
      this.xmlHTTP = new XMLHttpRequest();
    }
    catch (e)
    {
      // Internet Explorer
      try
      {
        this.xmlHTTP = new ActiveXObject("Msxml2.xmlHTTP");
      }
      catch (e)
      {
        try
        {
          this.xmlHTTP = new ActiveXObject("Microsoft.xmlHTTP");
        }
        catch (e)
        {
          // NO AJAX BABY
          this.xmlHTTP = null;
          return false;
        }
      }
    }
  }
  
  //
  // This is the callback that is used internally by the connector system to
  // manage the connector state. 
  //
  // This is NOT the callback that you should be redefining to do post processing.
  //
  this.callBack = function()
  {
    this.connectionState = this.xmlHTTP.readyState;
    
    //GlobalLog.debug("Connection State: " + this.connectionState);
    
    // Request completed
    if(this.connectionState == 4)
    {
      // Reset the callback
      this.xmlHTTP.onreadystatechange = resetAjaxDummy;
      //GlobalLog.debug("Response: " + this.xmlHTTP.responseText);
      // Call the callback to process the result
      if (this.responseCallback != null)
        this.responseCallback(new AjaxResponse(this.xmlHTTP), this.invoker);

      this.connectionState = CONNECTION_STATE_IDLE;
      
      return true;
    }
    
    // Incomplete, continue waiting for response
    return false;
  }
  
  // 
  // Sets the url
  //
  this.setUrl = function(url)
  {
    this.url = url;
  }
  
  //
  // Sets the callback function that will be called once the connection completes.
  // An AjaxResponse object will be passed to this function as a parameter.
  // If invoker is defined than it will be passed along as a second parameter to 
  // the callback. Can be thought of as a "this" to use in the context of the callback.
  //
  this.setResponseCallback = function(callback, invoker)
  {
    if (callback != undefined)
      this.responseCallback = callback;
    
    if (invoker != undefined)
      this.invoker = invoker;
  }
  
  //
  // Logs a message to the GlobalLog if available
  //
  this.logMessage = function(message)
  {
    if (GlobalLog != undefined)
      GlobalLog.debug(message);
  }
  
  // Finally create the request
  this.createRequest();
}

/**
 * AjaxConnectorFactory
 *
 * A connector factory that generates AjaxConnectors. Uses connection pooling to 
 * avoid leaking connections. A list of up to maxConnections connectors can be 
 * maintained for reuse. If no maxConnections is specified then 5 will be used. 
 * If the pool size is exceeded and no connectors are available then new connectors 
 * will be returned.
 */
function AjaxConnectorFactory(maxConnections)
{
  this.connectorPool = new Array();
  
  if (maxConnections == undefined)
    this.maxConnections = 5;
  else
    this.maxConnections = maxConnections;
  
  //
  // Gets a connector. Will create a new one if none are available in the pool.
  //
  this.getConnector = function()
  {
    var idleConnector = null;
    
    for (i in this.connectorPool)
    {
      if (this.connectorPool[i].connectionState == CONNECTION_STATE_IDLE)
      {
        // Found an idle connector in the pool so return it.
        idleConnector = this.connectorPool[i];
        this.logMessage("Reusing AjaxConnector");
        break;
      }
    }
    
    if (idleConnector == null)
    {
      // No idle connectors were in the pool so return a new one.
      this.logMessage("Creating New Connector");
      idleConnector = new AjaxConnector();
      
      // Store the connector in the pool if there is room
      if (this.connectorPool.length < this.maxConnections)
        this.connectorPool.push(idleConnector);
    }
    
    // Lock the connector so it can't be used until it is released
    idleConnector.connectionState = CONNECTION_STATE_LOCKED;
    
    return idleConnector;
  }
  
  //
  // Will clear out excess connections if there are morte than allowed in the pool.
  //
  this.cleanupExcessConnections = function()
  {
    while (this.connectorPool.size > this.maxConnections)
      this.connectorPool.pop();
  }
  
  //
  // Sets the maximum number of connections for the pool. If reduced, then excess 
  // connections will be eliminated.
  //
  this.setMaxConnections = function(maxConnections)
  {
    this.maxConnections = maxConnections;
    this.cleanupExcessConnections();
  }
  
  //
  // Logs a message to the GlobalLog if available
  //
  this.logMessage = function(message)
  {
    if (GlobalLog != undefined)
      GlobalLog.debug(message);
  }
}

/* 
 * Initialize a global connectorFactory variable so it will be available in all 
 * subsequent scripts.
 */
var AjaxFactory = new AjaxConnectorFactory();


