/*  DepressedPress.com DP_RequestPool

    Author: Jim Davis, the Depressed Press of Boston
    Date: June 22, 2006
    Contact: webmaster@depressedpress.com
    Website: www.depressedpress.com

    Full documentation can be found at:
    http://www.depressedpress.com/Content/Development/JavaScript/Extensions/

    DP_RequestPool regulates and manages multiple HTTP requests.

    Built-in Debugging requires DP_Debug (also available from the above address).  Run the DP_Debug.enable() to enable debugging, run DP_Debug.disable() to disable debugging.

    Copyright (c) 1996-2006, The Depressed Press of Boston (depressedpress.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. 

    +) Neither the name of the DEPRESSED PRESS OF BOSTON (DEPRESSEDPRESS.COM) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

    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.

*/


// The Request Object
DP_Request = function( Method, URL, Parameters, Handler, HandlerArgs, Timeout, MaxAttempts ) {

    // Manage Arguments
    if ( Method.toLowerCase() != "get" && Method.toLowerCase() != "post" ) {
	Method = "GET";
    };
    // Set the URL
    if ( typeof URL != "string" ) {
	URL = "";
    };
    // If present turn the Parameters object into a string
    var ParametersList = "";
    if ( typeof Parameters == "object" ) {
	for ( var CurProp in Parameters ) {
	    if ( typeof Parameters[CurProp] != "function" ) {
		ParametersList += CurProp + "=" + escape(Parameters[CurProp]) + "&";
	    };
	};
    };
    // Deal with the Handler
    if ( typeof Handler != "function" ) {
	Handler = null;
    };
    // Deal with the Handler Arguments
    if ( typeof HandlerArgs != "object" ) {
	HandlerArgs = [];
    };
    // Set the Timeout
    if ( typeof Timeout != "number" ) {
	Timeout = null;
    } else {
	Timeout = Timeout * 1000;
    };
    // Set the MaxAttempts
    if ( typeof MaxAttempts != "number" ) {
	MaxAttempts = null;
    };

    // Populate Properties
    this.Method = Method;
    this.URL = URL;
    this.Parameters = ParametersList;
    this.Handler = Handler;
    this.HandlerArgs = HandlerArgs;
    this.Timeout = Timeout;
    this.MaxAttempts = MaxAttempts;
    this.Attempts = 0;

    // Return
    return this;

};

// Request Pool Object
DP_RequestPool = function( RequesterCount, DefaultTimeout, DefaultMaxAttempts, Name ) {

    // Set ObInstanceCount
    if ( typeof RequesterCount != "number" || RequesterCount < 1 ) {
	RequesterCount = 4;
    };
    // Set DefaultTimeout
    if ( typeof DefaultTimeout != "number" || DefaultTimeout < 0 ) {
	DefaultTimeout = 0;
    };
    this.DefaultTimeout = DefaultTimeout * 1000;
    // Set MaxRequestAttempts
    if ( typeof DefaultMaxAttempts != "number" || DefaultMaxAttempts < 1 ) {
	DefaultMaxAttempts = 1;
    };
    this.DefaultMaxAttempts = DefaultMaxAttempts;
    // Set Name
    if ( typeof Name != "string" ) {
	Name = "DP_RequestPool";
    };
    this.Name = Name;

    // Create a pool of Requesters
    this.Requesters = new Array();

    // Create the Objects
    var CurInstance;
    for (var Cnt = 0; Cnt < RequesterCount; Cnt++) { 
	CurInstance = new DP_Requester();
	if ( CurInstance ) {
	    this.Requesters[this.Requesters.length] = CurInstance;
	} else {
	    throw new Error("DP_RequestPool was unable to instantiate compatible HTTPRequest objects.");
	};
    };

    // Create a Queue for Requests
    this.RequestQueue = new Array();
	
    // Add Debugging
    try {
	if ( DP_Debug.isEnabled() ) {
	    DP_Debug.logger("Request Pool Created.<br>" + this.Requesters.length + " " + this.Requesters[0].Type + " objects in pool.<br>Max Request Attempts: " + this.DefaultMaxAttempts + ".<br>Default Timeout: " + this.DefaultTimeout + ".", this.Name);
	};
    } catch (e) {};

    // The Requester Object
    function DP_Requester() {
	
	// Default the vars
	var CurInstance = null;
	var CurInstanceType = null;
	var CurRequester = null;
	// Attempt to find an instance of the request object that works
	try{
	    CurInstance = new XMLHttpRequest();
	    CurInstanceType = "XMLHttpRequest (Native)";
	} catch(e){
	    var IDs = [
		       'MSXML2.XMLHTTP.6.0',
		       'MSXML2.XMLHTTP.3.0',
		       'MSXML2.XMLHTTP',
		       'Microsoft.XMLHTTP'
		       ];
	    for ( var Cnt = 0; Cnt < IDs.length; Cnt++ ) {
		try {
		    CurInstance = new ActiveXObject(IDs[Cnt]);
		    CurInstanceType = IDs[Cnt] + " (ActiveX)";
		    break;
		} catch(e) {};
	    };
	};

	// Set up the Object
	if ( CurInstance ) {
	    this.Ob = CurInstance;
	    this.Type = CurInstanceType;
	    this.Request = null;
	    this.StartTime = null;
	    // Return the Requester
	    return this;
	} else {
	    // Return null
	    return null;
	};
	
    };

};

DP_RequestPool.prototype.isQueueEmpty = function() { 

    if ( this.RequestQueue.length == 0 ) {
	return true;
    } else {
	return false;
    };

};

DP_RequestPool.prototype.queueCount = function() { 

    return this.RequestQueue.length;

};

DP_RequestPool.prototype.clearQueue = function( AbortRunningRequests ) {

    // Manage Arguments
    if ( typeof AbortRunningRequests != "boolean" ) {
	AbortRunningRequests = false;
    };

    // Clear the current Queue
    this.RequestQueue = new Array();

    // Kill running requests
    if ( AbortRunningRequests ) {
	for ( var Cnt = 0; Cnt < this.Requesters.length; Cnt++ ) {
	    CurRequester = this.Requesters[Cnt];
	    // Abort the request (this will call the onReadyStateChange handler)
	    CurRequester.Ob.abort();
	    CurRequester.Request = null;
	    CurRequester.StartTime = null;
	};
    };

    // Add Debugging
    try {
	if ( DP_Debug.isEnabled() ) {
	    if ( AbortRunningRequests ) {
		DP_Debug.logger("Request Pool Cleared (Running Requests Aborted).", this.Name);
	    } else {
		DP_Debug.logger("Request Pool Cleared.", this.Name);
	    };
	};
    } catch (e) {};

    // Return true
    return true;

};

DP_RequestPool.prototype.isBusy = function() { 

    // Loop over the Requesters
    for ( var Cnt = 0; Cnt < this.Requesters.length; Cnt++ ) { 
	if ( this.Requesters[Cnt].Ob.readyState != 0 && this.Requesters[Cnt].Ob.readyState != 4 ) {
	    return true;
	};
    };
    // No objects were in the "busy" states
    return false;

};

DP_RequestPool.prototype.startInterval = function( IntervalCount ) { 

    // Manage Arguments
    if ( typeof IntervalCount != "number" ) {
	IntervalCount = 500;
    };

    // Set a proxy function to maintain scoping when called in the context of Window
    var ThisPool = this;
    var IntervalProxy = function() {
	ThisPool.doRequest();
	ThisPool.manageTimeouts();
    };
    // Start the Interval
    this.IntervalID = window.setInterval(IntervalProxy, IntervalCount);

    // Add Debugging
    try {
	if ( DP_Debug.isEnabled() ) {
	    DP_Debug.logger("Polling Interval Started (" + IntervalCount + " ms).", this.Name);
	};
    } catch (e) {};

};

DP_RequestPool.prototype.stopInterval = function() { 

    // Clear the Interval
    window.clearInterval(this.IntervalID);

    // Add Debugging
    try {
	if ( DP_Debug.isEnabled() ) {
	    DP_Debug.logger("Polling Interval Stopped.", this.Name);
	};
    } catch (e) {};

};

DP_RequestPool.prototype.addRequest = function( Request ) {

    // If needed update the request with defaults
    if ( !Request.Timeout ) {
	Request.Timeout = this.DefaultTimeout;
    }; 
    if ( !Request.MaxAttempts ) {
	Request.MaxAttempts = this.DefaultMaxAttempts;
    }; 

    // Add the Request to the Queue
    this.RequestQueue.push(Request);

    // Add Debugging
    try {
	if ( DP_Debug.isEnabled() ) {
	    DP_Debug.logger("Request Added.", this.Name);
	    DP_Debug.dump(Request, this.Name + " Request Added");
	};
    } catch (e) {};

    // Return
    return true;

};

DP_RequestPool.prototype.manageTimeouts = function() { 

    // Set local vars
    var CurRequester;
    // Determine if any Requesters have exceeded the Timeout
    for ( var Cnt = 0; Cnt < this.Requesters.length; Cnt++ ) {
	CurRequester = this.Requesters[Cnt];
	// Determine if the current Requester has exceeded its Timeout
	if ( CurRequester.StartTime && CurRequester.Request && CurRequester.Request.Timeout > 0 ) {
	    if ( ( new Date().getTime() - CurRequester.StartTime.getTime() ) > CurRequester.Request.Timeout ) {
		// If needed, retry the request
		if ( CurRequester.Request.Attempts < CurRequester.Request.MaxAttempts ) {
		    // Reset the onreadystatechange so that it's not called on abort
		    CurRequester.Ob.onreadystatechange = function() { };
		    // Update the Attempts
		    CurRequester.Request.Attempts = CurRequester.Request.Attempts + 1;
		    // Retry the Request
		    this.addRequest(CurRequester.Request);
		};
		// Abort the instance to clear it
		CurRequester.Ob.abort();
		// Add Debugging
		try {
		    if ( DP_Debug.isEnabled() ) {
			DP_Debug.logger("Request Timed Out.<br>" + CurRequester.Request.URL, this.Name);
		    };
		} catch (e) {};
		// Reset the Requester Props
		CurRequester.Request = null;
		CurRequester.StartTime = null;
	    };
	};
    };
    // Return true
    return true;

};

DP_RequestPool.prototype.doRequest = function() { 

    // See if there are any waiting requests
    if ( this.RequestQueue.length == 0 ) {
	// Return
	return false;
    };

    // Try to get an Available ObInstance
    var CurRequester, CurRequesterID;
    for ( var Cnt = 0; Cnt < this.Requesters.length; Cnt++ ) {
	if ( !this.Requesters[Cnt].Request && this.Requesters[Cnt].Ob.readyState == 0 ) {
	    CurRequester = this.Requesters[Cnt];
	    CurRequesterID = Cnt;
	    break;
	};
    };

    // If we have an available instance, use it
    if ( CurRequester ) {

	// Get the Request
	CurRequester.Request = this.RequestQueue.shift();
	// Set the time the request began
	CurRequester.StartTime = new Date();

	// Set a reference to "this"
	var ThisPool = this;
	// Set the state change handler
	CurRequester.Ob.onreadystatechange = function() {   
	    ThisPool.readyStateHandler(CurRequester);
	};

	// Open the Request
	CurRequester.Ob.open( CurRequester.Request.Method, CurRequester.Request.URL, true );
	// Set the encoding type, if needed
	if ( CurRequester.Request.Method.toLowerCase() == "post" ) {
	    CurRequester.Ob.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
	};

	// Send the data
	CurRequester.Ob.send(CurRequester.Request.Parameters);

	// Add Debugging
	try {
	    if ( DP_Debug.isEnabled() ) {
		DP_Debug.logger("Request Sent using Object " + CurRequesterID + ".<br>" + CurRequester.Request.URL, this.Name);
	    };
	} catch (e) {};

	// Return
	return true;

    };

    // Add Debugging
    try {
	if ( DP_Debug.isEnabled() ) {
	    DP_Debug.logger("Request Held (No available request objects).<br>" + CurRequester.Request.URL, this.Name);
	};
    } catch (e) {};

    // Return
    return false;

};

DP_RequestPool.prototype.readyStateHandler = function(Requester) {

    if ( Requester.Ob.readyState == 4 ) {
	if ( Requester.Ob.status != "200" && Requester.Ob.status != "0" && Requester.Request.Attempts < Requester.Request.MaxAttempts ) {
	    // Update the Attempts
	    Requester.Request.Attempts = Requester.Request.Attempts + 1;
	    // Retry the Request
	    this.addRequest(Requester.Request);
	} else {
	    if ( typeof Requester.Request.Handler == "function" ) {
		// Construct the onReadyStateChange handler
		var ArgString = [];
		for (var cnt = 0; cnt < Requester.Request.HandlerArgs.length; cnt++) {
		    ArgString[cnt] = "Requester.Request.HandlerArgs[" + cnt + "]";
		};
		var HandlerString = "";
		if ( ArgString.length == 0 ) {
		    HandlerString += "Requester.Request.Handler(Requester.Ob.responseText);";
		} else {
		    HandlerString += "Requester.Request.Handler(Requester.Ob.responseText, " + ArgString.join(", ") + ");";
		};
		// Run the handler
		eval(HandlerString);
	    };
	};
	// Abort the request (this will call the onReadyStateChange handler again so we'll clear it first)
	Requester.Ob.onreadystatechange = function() { };
	Requester.Ob.abort();
	// Reset the Requester
	Requester.Request = null;
	Requester.StartTime = null;
    };

};

