/* $Revision: 551 $ | $CreationDate: 2007-09-18 14:51:15 +0100 (Tue, 18 Sep 2007) $ | $LastChangedDate: 2010-07-18 16:35:26 +0000 (Sun, 18 Jul 2010) $ | $LastChangedBy: benroberts $ */

/**
 * CBSAjax is a namespace for our Ajax library.
 *
 */
var CBSAjax = {

	/*
	newRequest : function(url, handler, showProgress, method, postdata, showErrMsg, allowRetries)
	cleanup : function()
	decodeXMLEntities : function(s)
	getResult : function(xml)
	getSuccess : function(xml)
	getErrorMsg : function(xml)
	getErrorAllowRetries : function(xml)
	getDataNode : function(xml, value)
	getJSNode : function(xml, value)
	getNode : function(node, name, getvalue)
	getNodes : function(node, name, getvalue)
	getValue : function(node)
	*/

	/**
	 * The number of currently in progress requests that we need to show the progress dialog for
	 *
	 */
	requestsShowingProgress : 0,

	/**
	 * The number of seconds to wait before timing out an Ajax request
	 *
	 */
	timeoutPeriod : 30,

    /**
    * Whether we're in the ACP (if so, Ajax requests get an extra acp=1 GET arg)
    */
    acp : false,

	/**
	 * Creates a new Ajax request.
	 * The handler function must accept one parameter: a response object
	 *  (http_request,status,xml,success,errno,errmsg,data)
	 * Returns a boolean indicating whether the request was successfully sent.
	 *
	 * @param string URL to make request to
	 * @param function function to call to handle the response
	 * @param boolean Whether to show the "progress" dialog and prevent user input while request is in progress
	 * @param string HTTP method to use: either GET, POST or HEAD. (GET is default)
	 * @param object Object ("associative array") of post data name/value pairs
	 * @param boolean Whether to show an error message if JS errors occur or the Ajax server script is unreachable
	 * @param boolean Whether to allow retries if JS errors occur or the Ajax server script is unreachable
     * @param int The timeout period for this request (in seconds). Defaults to CBSAjax.timeoutPeriod if not specified
	 * @return boolean
	 */
	newRequest : function(url, handler, showProgress, method, postdata, showErrMsg, allowRetries, timeoutPeriod) {
		//Ensure that our method is uppercase and valid.  If not, assume GET
		if (typeof method == 'string') method = method.toUpperCase();
		if (method != 'GET' && method != 'POST' && method != 'HEAD') method = 'GET';
		if (showErrMsg == undefined) showErrMsg = showProgress;
		if (allowRetries == undefined) allowRetries = true;
        if (timeoutPeriod == undefined) timeoutPeriod = CBSAjax.timeoutPeriod;

		if (url.substring(0,1) != '/') url = '/' + url;
		var baseDir = CBS.getBaseDir();
		if (url.substring(0, baseDir.length) != baseDir) url = baseDir + url;

        if (CBSAjax.acp && url.indexOf('acp=1')<0) {
            if (url.indexOf('?')>=0) {
                url += '&acp=1';
            } else {
                url += '?acp=1';
            }
        }

		function handleFailure(erroredResult) {
			CBSGUI.isAjaxFailed = true;

			var hfErrMsg;
			var hfErrUrl;
			var hfShowError = showErrMsg;
			var hfAllowRetries = allowRetries;
			if (typeof erroredResult == 'string') {
				hfErrMsg = erroredResult;
			} else {
				hfErrMsg = erroredResult.errmsg;
				hfErrUrl = erroredResult.errurl;
				hfAllowRetries = erroredResult.errallowretries;
			}

			if (hfShowError) {

				if (hfAllowRetries) {
					function allowAjaxRetryCallback(result) {
						if (result) {
							CBSAjax.newRequest(url, handler, showProgress, method, postdata, showErrMsg, allowRetries);
						} else {
							handler(false);
						}
					}
					hfErrMsg += "\n\nDo you want to retry this request?";
                    CBSGUI.showConfirm(hfErrMsg, allowAjaxRetryCallback, "AJAX Request Failed", true, false, "Retry", "Cancel", null, true, null, null, CBSAjax.acp);
				} else {
					if (hfErrUrl) {
						CBSGUI.showAlert(hfErrMsg, function() { location.href=hfErrUrl; }, "AJAX Request Failed", false, null, null, true, null, null, CBSAjax.acp);
					} else {
						CBSGUI.showAlert(hfErrMsg, null, "AJAX Request Failed", false, null, null, true, null, null, CBSAjax.acp);
					}
					handler(false);
				}
			}
		}

		//Attempt to create the request object
		var http_request;
		if (window.XMLHttpRequest) {
			//Native XMLHttpRequest (Mozilla, Firefox, Safari, Opera, IE7, etc.)
			try {
				http_request = new XMLHttpRequest();
				if (http_request.overrideMimeType) {
                        http_request.overrideMimeType('text/plain');
                }
			} catch (e) {}
		} else if (window.ActiveXObject) {
			try {
				//IE 6
				http_request = new ActiveXObject("Msxml2.XMLHTTP");
			} catch (e) {
				try {
					http_request = new ActiveXObject("Microsoft.XMLHTTP");
				} catch (e) {}
			}
		}
		if (!http_request) {
			CBSGUI.isAjaxFailed = true;
			//Return false
			handleFailure("Unable to create an XMLHttpRequest object; please ensure that your browser supports XMLHttpRequest (Ajax).");
			return false;
		} else {
			CBSGUI.isAjaxFailed = false;
		}

		try {
			CBSGUI.isAjaxFailed = false;

			//Show the progress dialog if needed
			if (showProgress) CBSGUI.showLoadingDialog();

			//Open our request.  Always synchronously, though.
			http_request.open(method, url, true);

			//Define our ready state change fucntion
			http_request.onreadystatechange = function() {
					if (http_request.readyState == 4) {
						//Only do anything if the request has finished
						//.. and hide the progress dialog if necessary
						if (showProgress) CBSGUI.hideLoadingDialog();

						if (!http_request.CBSAjaxaborted) {
							//Call the required handler function, passing the request and its HTTP status
							try {
								var resp = {};
								resp.http_request = http_request;
								try{resp.status = http_request.status;}catch(e){resp.status=0;} //This errors with an unknown error after timing out :/
								if(http_request.responseText.search('<b>Fatal error</b>')!=-1 || http_request.responseText.search('<b>Parse error</b>')!=-1){
									resp.status = 500;
								}
								if (resp.status == 200) {
								    if (!http_request.responseText) {
								        handleFailure("The Ajax request has failed: The server did not send a valid response.");
								        return false;
								    }
								    
									var result = CBS.jsonDecode(http_request.responseText);
									resp.success = result.s;
									resp.errmsg = result.e.m;
									resp.errurl = result.e.u;
									resp.errallowretries = result.e.r;
									resp.data = result.d;
									resp.js = result.j;

									if (resp.errmsg == null) {
										if (resp.success) {
											handler(resp);
											if (resp.js) {
												for (var i=0;i<resp.js.length;i++) {
													try {
														eval(resp.js[i]);
													} catch (ex) {
														console.log(ex);
													}
												}
											}
										} else {
											handler(false);
										}
									} else {
										handleFailure(resp);
									}
								} else if (resp.status > 0 && resp.status < 10000) {
									//Sometimes we'll get silly 4-billion-odd status numbers if the request is interrupted
									// so let's limit our status error-handling to 10,000.
									//Yes, I'm aware that the silly 4-billion-odd number is someone trying to shove -1 into an unsigned 32-bit int...
									var statusErrMsg = "The server returned a HTTP status code of '"+resp.status+"', which is not the '200' status that was expected.";

									switch (resp.status) {
										case 401:
											statusErrMsg = "You are not permitted to perform such an AJAX equest as correct login credentials were not provided. This is not related to your store login.";
											break;
										case 403:
											statusErrMsg = "You are not permitted to perform such an Ajax request. This is not related to your store login.";
											break;
										case 404:
											statusErrMsg = "The AJAX request handler file could not be found on the server.  This may be a temporary problem, or it may be an error with the store set-up.";
											break;
										case 500:
											statusErrMsg = "An unexpected error occurred in the AJAX request handler file on the server. This may be a temporary error, or it may be a problem with the store set-up.";
											break;
									}

									handleFailure(statusErrMsg);
								}
							} catch (e) {
								handleFailure("The following JavaScript error occurred while processing the Ajax response:\n["+e.name+" on line "+e.lineNumber+" of "+e.fileName+"] "+e.message);
							}
						}

						//Then unset the http request in an attempt to not leak memory
						http_request = null;
					}
				};

			//Send a header that sort-of claims this request is valid
		    http_request.setRequestHeader('X-CBS', 'true');
		    http_request.setRequestHeader('X-CBS-Skin', CBS_Settings.SkinName);

			//Do we need to send post data?
			if (postdata != undefined) {
				//Yes; prepare it!
			    http_request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');

			    //Loop through the post data
			    if (typeof postdata == 'object') {
			    	var pd = new Array();
			    	for (var i in postdata) {
			    	    if (i == '_formFields') {
			    	        // Add each field to the pd array. How we find the value of a field depends on its type. 
			    	        for (var field_i=0, field_l=postdata[i].length; field_i<field_l; field_i++) {
			    	            var field = postdata[i][field_i];
			    	            // Skip any nameless fields, since these wouldn't be sent by the browser either
			    	            if (!field.name) continue;
			    	            
			    	            switch (field.tagName.toLowerCase()) {
    			                case 'input':
    			                    switch (field.type) {
    			                    case 'text':
    			                    case 'password':
    			                        if (!field.value) continue;
    			                        pd.push(field.name + '=' + encodeURIComponent(field.value));
    			                        break;
    			                    case 'radio':
    			                    case 'checkbox':
    			                        if (!field.checked) continue;
    			                        pd.push(field.name + '=' + encodeURIComponent(field.value));
    			                        break;
    			                    default:
    			                        // Button, submit, image etc, ignore it
    			                        continue;
    			                    }
    			                    break;
    			                case 'select':
    			                    if (field.multiple) {
    			                        for (var opt_i=0,opt_l=field.options.length; opt_i<opt_l; opt_i++) {
    			                            if (field.options[opt_i].selected) {
    			                                pd.push(field.name + '=' + encodeURIComponent(field.options[opt_i].value));
    			                            }
    			                        }
    			                    } else {
    			                        if (field.selectedIndex < 0) continue;
    			                        pd.push(field.name + '=' + encodeURIComponent(field.options[field.selectedIndex].value));
    			                    }
    			                    break;
    			                case 'textarea':
    			                    if (!field.value) continue;
    			                    pd.push(field.name + '=' + encodeURIComponent(field.value));
    			                    break;
    			                
    			                default:
    			                    // Unrecognised field type, ignore it
    			                    continue;
    			                }
			    	        }
			    	    } else if (typeof postdata[i] == 'object' && typeof postdata[i].push != 'undefined') {
			    			//array
			    			for (var ii=0,l=postdata[i].length; ii<l; ii++) {
			    				pd.push(i + '[]=' + encodeURIComponent(postdata[i][ii]));
			    			}
			    		} else {
			    			pd.push(i + "=" + encodeURIComponent(postdata[i]));
			    		}
			    	}
			    	postdata = pd.join('&');
			    } else if (typeof postdata != 'string') {
			    	//Or we could accept a string, which would just be passed straight through
			    	console.log("Attempting to send invalid post data. Only objects and strings are valid.");
			    	return false;
			    }

			    //Send our request
			    http_request.send(postdata);
			} else {
				//Send the request sans post data
			    http_request.send(null);
			}

			function handleTimeout() {
				if (http_request && http_request.readyState < 4 && http_request.readyState > 0) {
					http_request.CBSAjaxaborted = true;
					http_request.abort();
					handleFailure("That Ajax request timed out.  This could be caused by excessive load on the server or lack of Internet connectivity for your computer.");
				}
			}
			window.setTimeout(handleTimeout, timeoutPeriod*1000);

			//If we've got this far, it seems pretty successful, so return true!
			return true;
		} catch (e) {
			//An error occurred :(
			//Hide the dialog, call the error function, then fail
			if (showProgress) CBSGUI.hideLoadingDialog();
			http_request = null;
			handleFailure("The following JavaScript error occurred while processing the Ajax request:\n["+e.name+" on line "+e.lineNumber+" of "+e.fileName+"] "+e.message);
			return false;
		}
	},

	/**
	 * Repositions and resizes the surround and alert box divs.
	 * Can be called when the window is resized or scrolled, and also when
	 *  the dialog is initially created.
	 *
	 */
	positionDialog : function() {
		var d = CBS.getEl('dimmerlayer');

		if (d.style.display != 'block') return;

		var dchildren = d.getElementsByTagName('img');
		var img = dchildren[0];

		var cd = CBS.getClientDimensions();
		var cso = CBS.getClientScrollOffset();
		var surWidth = parseInt(cd.width);
		var bodyWidth = CBS.getBody().offsetWidth;
		if (surWidth > bodyWidth) surWidth = bodyWidth;
		d.style.width = surWidth + 'px';
		d.style.height = cd.height + 'px';
		d.style.top = cso.y + 'px';
		cso.x = parseInt(cso.x) - 12;
		d.style.left = cso.x + 'px';

		img.style.marginTop = ((cd.height - img.offsetHeight) / 2) + 'px';
	}

};