/*
 * Retrieves an XmlHttp Object for AJAX use.
 */
function GetXmlHttpObject() {
	var xmlHttp;

	try {
		// Firefox, Opera 8.0+, Safari
		xmlHttp = new XMLHttpRequest();
	}
	catch (e) {
		// Internet Explorer
		try {
			xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch (e) {
			try {
				xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch (e) {
				//alert("Your browser does not support AJAX!");
			    alert (ajax_nosupport);
				return false;
			}
		}
	}

	return xmlHttp;
}

/* 
 * Retrieves the "top" most frame of DSCtop. This would be the first nested
 * frameset that contains the header bar, the menu links, and the content area.
 */
function getTopFrame() {
	var frame = window;
	var frameset = null;
	
	while (true) {
		frameset = frame.document.getElementById("frmoperate");
		if (frameset) {
			return frame;
		}
		
		// not in DSCtop's framed structure (a popup, new window or new tab)
		if (frame == frame.parent) {
			return null;
		}

		frame = frame.parent;
	}
}

/* 
 * This function is used to display a spinner over the entire content frame of
 * DSCtop. It is useful for hiding large DOM manipulations and preventing user
 * interaction during an AJAX call.
 */
function displayLongCallOverlay(noCancel) {
	var hideCancel = noCancel || false;	//only hide for session timeout
	var frame = getTopFrame();
	
	try {
		var spinner = jQuery('#spinner', frame.framemid.document);
		jQuery(spinner).css('display', 'block');
		// prevents broken spinner in MSIE (after a JS error)
		jQuery(spinner).attr('src', context_path + '/public/images/ajax_long_action.gif');
		var msg_div = jQuery('#ajax_long_call_message', frame.framemid.document);
		jQuery(msg_div).css('display', 'block');
		if (!hideCancel) {
			jQuery('#cancel_link', frame.framemid.document).css('display', 'block');
		}
		var frameset = frame.document.getElementById('frmoperate');
		var parts = frameset.cols.split(',');
		frameset.cols = parts[0] + ',*,0';
	} catch (e) {}
}

/*
 * Hides the long call overlay spinner that covers the content area. 
 */
function hideLongCallOverlay() {
	var frame = getTopFrame();
	
	try {
		jQuery('#spinner', frame.framemid.document).css('display', 'none');
		jQuery('#ajax_long_call_message', frame.framemid.document).css('display', 'none');
		jQuery('#cancel_link', frame.framemid.document).css('display', 'none');
		var frameset = frame.document.getElementById('frmoperate');
		var parts = frameset.cols.split(',');
		frameset.cols = parts[0] + ',10,*';
	} catch (e) {}
}

/*
 * Sets the message displayed when the long call overlay is used. 
 */
function setLongCallMessage(message) {
	var frame = getTopFrame();
	
	try {
		jQuery('#ajax_long_call_message', frame.framemid.document).text(message);
	} catch (e) {}
}


/*
 * Returns the specific network error AJAX has encountered. These error codes
 * only apply to Internet Explorer 6-9+
 */
function getNetworkErrorDesc(code) {
	switch (code) {
	case 1223:		return "client canceled request";
	case 12002:		return "connection timed out";
	case 12029:		return "cannot connect to server";
	case 12030:		return "connection to server aborted";
	case 12031:		return "connection to server has been reset";
	case 12152:		return "invalid server response";
	case 12164:		return "cannot reach server";
	default:		return false;
	}
}

/*
 * Makes a GET call to the URL passed, using the arguments passed.
 * 
 * The arguments will be formatted into a query string and passed to the URL
 * using GET. Errors (500, 404, etc) are handled within the function. A
 * successful AJAX call (HTTP-200) results in the successHandler being executed.
 * The handler expects one parameter which is an array of query string key-value
 * pairs with the AJAX responseText stored in the value "result". Network errors
 * are handled by waiting 5 seconds and trying to execute the AJAX call again
 * for up to 5 times before finally giving up.
 * 
 * Parameters:
 * URL				The URL of the page to call.
 * args				An array or object of arguments to be passed to the URL
 * successHandler	Only called when the action returns with an HTTP-200 status
 * completeHandler	always called after the AJAX event takes place
 */
function AJAXGetRequest(url, args, successHandler, completeHandler) {
	args = args || new Array();
	var succHandler = successHandler || function(args) {};
	
	// prevents browser from caching a seemingly static GET request
	args['smash_that_cache'] = Number(new Date());
	AJAXRequest("GET", url, args, succHandler, completeHandler);
}


/*
 * Makes a POST call to the URL passed, using the arguments passed.
 * 
 * The arguments will be formatted into a query string and passed to the URL
 * using POST. Errors (500, 404, etc) are handled within the function. A
 * successful AJAX call (HTTP-200) results in the successHandler being executed.
 * The handler expects one parameter which is an array of query string key-value
 * pairs with the AJAX responseText stored in the value "result". Network errors
 * are handled by waiting 5 seconds and trying to execute the AJAX call again
 * for up to 5 times before finally giving up.
 * 
 * Parameters:
 * URL				The URL of the page to call.
 * args				An array or object of arguments to be passed to the URL
 * successHandler	Only called when the action returns with an HTTP-200 status
 * completeHandler	always called after the AJAX event takes place
 */
function AJAXPostRequest(url, args, successHandler, completeHandler) {
	args = args || new Array();
	var succHandler = successHandler || function(args) {};
	
	AJAXRequest("POST", url, args, succHandler, completeHandler);
}


function AJAXRequest(method, url, args, successHandler, completeHandler, attemptNo) {
	var showOverlay = false;
	var doneHandler = function() {};
	if (completeHandler != undefined) {
		var objType = (typeof completeHandler);
		switch (objType) {
		case "boolean":
			if (completeHandler) {
				showOverlay = true;
				doneHandler = hideLongCallOverlay;
			}
		break;
		case "function":
			doneHandler = completeHandler;
		break;
		}
		
	}
	var numAttempts = attemptNo || 1;
	var q_string = BuildQueryString(args, false);
	var AJAX = GetXmlHttpObject();
	if (!AJAX) {
		alert('failed to create AJAX request.');
		return;
	}
	
	var wait_div = null;
	if (showOverlay && numAttempts == 1) {
		displayLongCallOverlay();
	}

	// process the server response
	AJAX.onreadystatechange = function() {
		if (AJAX.readyState == 4) {
			var success = (AJAX.status == 200);
			var retry = false;
			var netError = false;
			var maxAttempts = 5;
			var errMsg = '';
			
			if (!success) {
				switch (AJAX.status) {
				case 500:	// something broke
					if (args['debug'] != undefined) {
						var callStack = AJAX.responseText.substring(668);
						errMsg = callStack.split('-->')[0];
					} else {
						errMsg = 'Call returned unexpected code ' + AJAX.status;
					}
				break;
				case 404:	// quoth the server "404"
					errMsg = 'File not found: "' + url + '"';
				break;
				case 410:	// session timed out, execute redirection
					var redirectReason = getRedirectReason(AJAX.responseText);
					displayRedirectMsg(redirectReason);
					return;
				break;
				
				default:
					// uncommon error, not appearing to be network related
					if (AJAX.status > 0 && AJAX.status != 1223 && AJAX.status < 12000) {
						errMsg = 'Call returned unexpected code ' + AJAX.status;
						if (AJAX.statusText.length > 0) {
							errMsg += '(' + AJAX.statusText + ')';
						}
						errMsg += '.';
					} else {
						// error with network assumed (0, 1223, 12000+)
						netError = true;
						retry = numAttempts <= maxAttempts;
						if (!retry) {
							// give up trying to execute action, network is down
							var errMsg = 'Network error. Action failed after '
								+ maxAttempts + ' tries';
							if (AJAX.status > 0) {
								errMsg += ' (' + AJAX.status;
								var errDesc = getNetworkErrorDesc(AJAX.status);
								if (errDesc !== false) {
									errMsg += ': ' + errDesc;
								}
								errMsg += ')';
							} else if (AJAX.statusText.length > 0) {
								errMsg += ' (' + AJAX.statusText + ')';
							}
							errMsg += '.';
						}
					}
				break;
				}
			}
			
			// either successful call or done trying to execute (network error)
			if (success || !netError || !retry) {
				try {
					doneHandler();
				} catch (e) {}
			}
			
			if (netError) {
				if (retry) {
					// wait 5 seconds and try again
					setTimeout(function() {
						AJAXRequest(method, url, args, successHandler,
							doneHandler, numAttempts + 1); }, 5 * 1000);
				}
			}
			
			// 200 status, run success handler
			if (success) {
				try {
					args['result'] = AJAX.responseText;
					successHandler(args);
				} catch (e) {				
					var sTrace = '';
					if (e.stack) {
						var cStack = getStackTrace(e);
						sTrace = '\n\n' + cStack.join('\n');
					}
					
					var context = '';
					var funcName = resolveFuncName(handler);
					if (args['debug'] != undefined) {
						context =  '\tURL: ' + url + '\n\n';
						context += '\tArguments:\n';
						for (key in args) {
							if (key == 'debug' || key == 'result') {
								continue;
							}
							context += '\t\t' + key + ' => "' + args[key] + '"\n';
						}
						context += '\n';
					}
					
					alert(funcName + '() failed:\n\n'
						 + context + '\t' + e.message + sTrace);
				}
			} else if (errMsg.length > 0) {
				alert(errMsg);
			}
			
			// clean up AJAX request after use
			// IE6 doesn't agree with setting this event to null so set it to an empty function.
			AJAX.onreadystatechange = function() {}; 
			delete AJAX;
			AJAX = null;
		}
	};
	
	// send request into server
	var request = (method == "GET" ? BuildRequest(url, args) : url);
	try {
		AJAX.open(method, request, true);
	} catch (e) {
		var errMsg = (method == "GET" ? request : request + "?" + q_string);
		alert('AJAX.open() failed: ' + e.message + '\n\nreq: ' + errMsg);
		return;
	}
	AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	if (method == "POST") {
		AJAX.setRequestHeader("Content-length", q_string.length);
		AJAX.send(q_string);
	} else {
		AJAX.send(null);
	}
	
	// store a global reference if possible (to allow cancel of request)
	var frame = getTopFrame();
	if (frame != null) {
		frame.AJAX = AJAX;
	}
}


function BuildQueryString(args, showQMark) {
	var q_string = "";
	if (args == null || args == undefined)
		return q_string;
	
	var first = true;
	for (var key in args) {
		if (first) {
			first = false;
			if (showQMark) {
				q_string += "?";
			}
		}
		else {
			q_string += "&";
		}
		q_string += escape(key) + "=" + escape(args[key]).replace(new RegExp("\\+", "g"), "%2B");
	}
	
	return q_string;
}

/*
 * This function can be used to create a properly escaped request string. Simply
 * pass in arguments in the form of an associative array, and this function will
 * return a fully escaped request string.
 */
function BuildRequest(action, arguments) {
	return action + BuildQueryString(arguments, true);
}


function resolveFuncName(func) {
	var needle = 'function ';
    var funcName = func.toString().substring(needle.length);
    needle = '(';
    funcName = funcName.substring(0, funcName.indexOf(needle));
    
	return funcName;
}


function getRedirectReason(errorPage) {
	var parts, reason;
	
	if (errorPage != undefined && errorPage != null && errorPage.trim().length > 0) {
		parts = errorPage.split(/REGEXMARKER/);
		reason = parts[1];
	}
	if (reason == undefined) {
		reason = 'session timed out';
	}
	
	return reason;
}


/* 
 * Code to handle expired sessions or an offline database.
 * Shows notification screen for a moment before redirecting to login.
 */
function displayRedirectMsg(redirectReason) {
	var delay = 3 * 1000;	// 3 seconds before redirect

	var topFrame = getTopFrame();
	if (!topFrame) {
		setTimeout(function() {
			window.location.href = context_path + '/index.jsp';
		}, delay);
	}
	else {
		setLongCallMessage(redirectReason);
		displayLongCallOverlay(true);
		setTimeout(function () {
			try {
				var frame = getTopFrame();
				if (frame != null) {
					frame.location = context_path + '/index.jsp';
				} else {
					window.location = context_path + '/index.jsp';
				}
			} catch (e) {}
		}, delay);
	}
}
