/* ajaxrequest.js

An object to make AJAX easy.  You use it this way:

	// Define a callback function in the following format:
	function MyResponseHandlerXYZ(response)
	{
		// Do response processing here
	}

	// Optionally, you can create a function to clean up if there's an error
	function MyErrorHandlerABC(optional_error_code)
	{
		// Cleanup from unsuccessful transaction here
	}

	// Instantiate the object, passing it the script it'll talk to
	var ajax = new AjaxRequest("/path/to/script.php");

	// Set up our params to pass to the script
	var params = new Array();
	params["formfield1"] = "jimbo123";
	params["formfield2"] = 456;

	// Do the HTTP request - first two args are mandatory, 3rd is optional
	if(ajax.Send(params, MyResponseHandlerXYZ, MyErrorHandlerABC))
	{
		// Send was successful, wait for response!
	}
	else
	{
		// Send didn't go through.  Bummer.
	}

That's all there is to it!

NOTE: Using AjaxRequest assumes that the server is sending back one (and only one) field called "status" that corresponds to the status codes below.  It also assumes that if there was an error, it'll send back a descriptive error message in the "errormsg" field, suitable to be displayed in an alert().

*/

function AjaxRequest(url)
{
	// Wait this many seconds before considering it an error
	var ERROR_TIMEOUT = 30;

	// Our HTTP response needs to contain one of these
	var STATUS_ERROR 			= 0;
	var STATUS_SUCCESS 			= 1;
	var STATUS_ERROR_SILENT		= 2;

	this.linked_object = null;
	
	this.LinkObject = function(link_to)
	{
		this.linked_object = link_to;
	}

	// PUBLIC - function to actually do the AJAX communication
	this.Send = function(params_array, success_handler, error_handler)
	{
		// Do some basic parameter checking
		if((url == "") || (typeof success_handler != "function") || ((params_array != null) && (AssociativeArrayIsEmpty(params_array))))
		{
			return(false);
		}

		// Check for an error callback
		if(typeof error_handler != "function")
		{
			error_handler = null;
		}

		// See if this browser supports AJAX.  It may not (though unlikely).
		var http_request = GetHttpRequestObject();
		http_request.linked_object = this.linked_object;
		
		if(http_request == null)
		{
			alert("Your browser is too old to use this feature.\n\nTo upgrade, visit:\nhttp://www.getfirefox.com");
			return(false);
		}

		// Kick off our function to watch for a timeout
		var timeout_watch = setTimeout(function() {
			// Take away this guy's response handler, in case the response is just really late
			http_request.onreadystatechange = function() {};

			// Inform the user of the timeout
			alert("We were unable to contact the Newgrounds server.  Please wait a moment and try again.");

			// If we've got a callback, call it
			if(error_handler != null)
			{
				error_handler(null, this.linked_object);
			}
		}, (ERROR_TIMEOUT * 1000));

		// Closures make for a beautiful way to handle AJAX responses, no globals needed!
		http_request.onreadystatechange = function() {
			if(http_request.readyState == 4)
			{
				if(http_request.status == 200)
				{
					// Cancel our earlier timeout watcher, since we got a response
					clearTimeout(timeout_watch);

					// Check the response for validity
					switch (http_request.responseText.charAt(0)) {
						case "<":
							response = new AjaxResponse(http_request, this.linked_object);
							break;
						default :
							response = new JSONResponse(http_request, this.linked_object);
							break;
					}
					
					if(HttpResponseIsValid(response, error_handler))
					{
						// If it's valid, call our handler
						success_handler(response);
					}
				}
				// Possibly handle other HTTP response codes here?
			}
		}

		// Now let's build a query string of our data
		var query_string = "";

		if(params_array != null)
		{
			for(var params_key in params_array)
			{
				// If you're using Prototype.js, your array will have a bunch of extra stuff you don't want
				if(typeof params_array[params_key] != "function")
				{
					query_string += "&" + URLEscape(params_key) + "=" + URLEscape(params_array[params_key]);
				}
			}

			// Hack off the leading "&"
			query_string = query_string.substring(1);

			// If we have a security key, add it on the end
			var security_key = document.getElementById("userkey");
			if(security_key)
			{
				query_string += "&userkey=" + URLEscape(security_key.value);
			}
		}

		// Right here is the magic part of AJAX - do the HTTP request
		http_request.open("POST", url, true);
		http_request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
		http_request.send(query_string);

		// We're done here.  Our handler will pick up when we get a response back.
		return(true);
	}

	// PRIVATE - function to get the super-magical XMLHttpRequest object that's key to AJAX
	function GetHttpRequestObject()
	{
		var new_request = null;

		if(window.XMLHttpRequest)		// For smart browsers
		{
			new_request = new XMLHttpRequest();
		}
		else if(window.ActiveXObject)	// For IE (lame)
		{
			try {
				new_request = new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch(e) {
				// If we're here, we couldn't make that object - try another one
				try {
					new_request = new ActiveXObject("Microsoft.XMLHTTP");
				}
				catch(e) {
					// We absolutely can't do AJAX.  Sorry.
					new_request = null;
				}
			}
		}

		return(new_request);
	}

	// PRIVATE - function to check the integrity of the XML received by the server
	function HttpResponseIsValid(response, error_handler)
	{
		// Did we get valid XML in our response?
		var status = response.GetField("status");
		if(status == null)
		{
			// Probably a PHP error - somehow, we didn't get valid XML back.
			alert("Server error.  Please wait a moment and try again.");

			// If we're on staging, be a pal and display the error here
			if(IsStaging())
			{
				alert(response.ToString());
			}

			if(error_handler != null)
			{
				error_handler();
			}

			return(false);
		}

		// We have valid XML.  What's our status?
		if(status == STATUS_ERROR)
		{
			// Application error.
			alert("Error - " + response.GetField("errormsg"));

			if(error_handler != null)
			{
				// errorcode will be null if there is none
				error_handler(response.GetField("errorcode"), response.GetLinkedObject());
			}

			return(false);
		}
		else if(status == STATUS_ERROR_SILENT)
		{
			// Just return false, don't do any error displaying
			return(false);
		}

		return(true);
	}

	function IsStaging()
	{
		return(window.location.href.indexOf("staging.newgrounds.com") >= 0 || window.location.href.indexOf("ng-local.newgrounds.com") >= 0);
	}

	// PRIVATE - utility function to see if an associative array's empty or not
	function AssociativeArrayIsEmpty(arr)
	{
		for (var key in arr)
		{
			if(typeof arr[key] != "function")	// This is to combat the stuff Prototype adds
			{
				// If there's one thing here, it's not empty
				return(false);
			}
		}

		return(true);
	}

	// PRIVATE - an improved version of escape() for sending our data across
	function URLEscape(text)
	{
		text = escape(text);
		text = text.replace(/\+/g, "%2B");		// Normal escape() ignores plus signs.
		return(text);
	}
	
	// This object represents the JSON response we'll get back from the server
	function JSONResponse(http_response, linked)
	{
		// Use this as a shortcut - it'll remember if it's validated or not, after the first test
		var validated = null;
		
		var json_object = null;
		
		if (http_response.responseText.isJSON()) {
			var json_object = http_response.responseText.evalJSON();
		} else {
			var json_object = null;
		}

		this.GetLinkedObject = function()
		{
			return(linked);
		}
		
		// Use this to get the value of a field that only appears once in the response
		this.GetField = function(fieldname)
		{
			var field = null;
			if(ResponseIsValid())
			{
				if (json_object && json_object[fieldname] != undefined) {
					return (json_object[fieldname]);
				}
			}

			return (null);
		}
		
		this.GetObject = function()
		{
			return (json_object);
		}

		// Checks the basic integrity of the DOM in the http_response object
		function ResponseIsValid()
		{
			// This keeps us from doing the tests below repeatedly
			if(validated == null && (http_response) && (json_object))
			{
				validated = true;
			}

			return(validated);
		}

		// Useful for debugging
		this.ToString = function()
		{
			return(http_response.responseText);
		}
	}
	
	// This object represents the XML response we'll get back from the server
	function AjaxResponse(http_response, linked)
	{
	
		// Use this as a shortcut - it'll remember if it's validated or not, after the first test
		var validated = null;
		
		this.GetLinkedObject = function()
		{
			return(linked);
		}
		
		// Use this to get the value of a field that only appears once in the response
		this.GetField = function(fieldname)
		{
			var field = null;
			if(ResponseIsValid())
			{
				// Now check for this specific field
				var field_arr = this.GetNodes(fieldname);
				if((field_arr) && (field_arr.length == 1) && (field_arr[0].firstChild) && (field_arr[0].firstChild.data) && (field_arr[0].firstChild.data != ""))
				{
					field = field_arr[0].firstChild.data;
				}
			}

			return(field);
		}

		// This function returns full-fledged DOM nodes
		this.GetNodes = function(nodename)
		{
			var nodes = new Array();
			if(ResponseIsValid())
			{
				nodes = http_response.responseXML.documentElement.getElementsByTagName(nodename);
			}

			return(nodes);
		}

		// Checks the basic integrity of the DOM in the http_response object
		function ResponseIsValid()
		{
			// This keeps us from doing the tests below repeatedly
			if(validated == null)
			{
				validated = (http_response) && (http_response.responseXML) && (http_response.responseXML.documentElement);
			}

			return(validated);
		}

		// Useful for debugging
		this.ToString = function()
		{
			return(http_response.responseText);
		}
	}
}
