var g_VIEW_MOBILE_LINK_TITLE = 'View approximation of link in mobile phone "viewer".\nNote: Some links may not render accurately, or at all.';
var g_APP_NAME = 'Lincoln';

function is_valid_numeric_key(nKeyCode)
{
	if (!is_control_key(nKeyCode) &&
		(nKeyCode != 46) &&	// Delete
		(nKeyCode != 8)  &&	// Backspace
		(nKeyCode < 96 || nKeyCode > 105) &&	// 0 - 9 on keypad
		(nKeyCode < 48 || nKeyCode > 57))		// 0 - 9
		return false;
	else
		return true;
}


function is_control_key(nKeyCode)
{
	if (nKeyCode != 37 &&	// Left cursor
		nKeyCode != 39 &&	// Right cursor
		nKeyCode != 9  &&	// Tab
		nKeyCode != 16 &&	// Shift
		nKeyCode != 35 &&	// End
		nKeyCode != 36) 	// Home
		return false;
	else
		return true;
}
		

function trim(sValue)
{
	// Strip out whitespace at beginning and end...
	return sValue.replace(/^\s+|/, '').replace(/\s+$/, '');
}


function is_supported_file_type(filename)
{
	if (filename.length > 3)
	{
		var sExtension = filename.substr(filename.length - 3, 3).toLowerCase();

	 	if ((sExtension == 'jpg') || (sExtension == 'gif') || (sExtension == 'bmp'))
			return true;
	}
	else
		return false;
}


function get_cookie(sKey)
{
	var cookies = document.cookie.split('; ');
	var sValue = null;
	var i;
	
	for (i = 0; i < cookies.length; i++)
	{
		// Is key present with data?
		if (cookies[i].indexOf(sKey + '=') == 0)
		{
			sValue = cookies[i].substring(cookies[i].indexOf('=') + 1, cookies[i].length);
			break;
		}
		else
		{
			// Is key present without data?
			if (cookies[i] == sKey)
			{
				sValue = '';
				break;
			}
		}
	}

	// alert('get_cookie(' + sKey + ') = ' + sValue);
	return sValue;
}


function set_session_cookie(sKey, sValue)
{
	document.cookie = sKey + '=' + sValue;
	// alert('set_session_cookie [after]: [' + document.cookie + ']');
}


function set_permanent_cookie(sKey, sValue) // Permanent = 1 year expiration
{
	var expiration = new Date();
	expiration.setUTCFullYear(expiration.getUTCFullYear() + 1);

	document.cookie = sKey + '=' + sValue + '; expires=' + expiration.toUTCString();
	// alert('set_permanent_cookie [after]: [' + document.cookie + ']');
}


function delete_cookie(sKey)
{
	var expiration = new Date();
	expiration.setTime(expiration.getUTCHours() - 24); // -1 day
	document.cookie = sKey + '=0; expires=' + expiration.toUTCString();
}


function verify_url_syntax(sUrl)
{
	if (sUrl.match(/^(https|http):\/\/([_a-z\d\-]+(\.[_a-z\d\-]+)+)(([_a-z\d\-\\\.\/]+[_a-z\d\-\\\/])+)*/i) == null)
		return false;
	else
		return true;
}


function verify_url(sLinkType, sUrl)
{
	var oReq = null;
	var fValid = false;
	var sHttpStatus = 'Unknown failure';

	// First, do a basic syntactic check...
	if (!verify_url_syntax(sUrl))
		return false;
	
	// Now try to actually visit the URL [via verifyURL.aspx]...
	
	if (window.XMLHttpRequest)
		oReq = new XMLHttpRequest();
	else
	{
		if (window.ActiveXObject)
			oReq = new ActiveXObject("Microsoft.XMLHTTP");		
	}
	
	if (oReq == null)
		return true; // Assume URL is valid
	else
	{
		window.status = 'Verifying URL...'; // Note: Will not display if we're called from a dialog
		document.body.style.cursor = 'wait';

		try
		{
			sUrl = document.URL.substr(0, document.URL.lastIndexOf('/')) + '/VerifyURL.aspx?LinkType=' + escape(sLinkType) + '&URL=' + escape(sUrl);
			oReq.open('GET', sUrl, false); // Synchronous
			oReq.send();

			if (oReq.readyState == 4)
				fValid = (oReq.status == 200);
				
			sHttpStatus = oReq.statusText;
		}
		catch (e)
		{
			var sMsg = (e.description != undefined) ? e.description : e;
			
			// Note: We use ERROR_MODAL here since if we don't we will miss the error altogether if the caller also 
			// calls message_box() to display our return value. However, be aware that there are very few conditions
			// that will ever lead to entering this code branch, so the "ugly" message box will rarely be seeen.
			message_box('ERROR_MODAL', 'MSR ' + g_APP_NAME + ' : Verify URL', 'Unable to verify URL (reason: ' + sMsg + ').');
		}

		document.body.style.cursor = 'default';
		window.status = '';
	}
	return (fValid ? 'OK' : sHttpStatus);
}


function centered_dialog_attribute(nDialogWidth, nDialogHeight, fIsNonModalDialog)
{
	var sAttribute = '';
	var fParentIsDialog = (!isNaN(parseInt(window.dialogTop)) && !isNaN(parseInt(window.dialogHeight)));
	var nParentBodyHeight = !fParentIsDialog ? document.body.clientHeight : parseInt(window.dialogHeight);
	var nParentBodyWidth = document.body.clientWidth;
	var nParentScreenTop = window.screenTop;
	var nParentScreenLeft = window.screenLeft;

	if (fIsNonModalDialog == undefined)
		fIsNonModalDialog = false;

	// On IE7 the requested width (nDialogWidth) is the content (interior) width, so we have to add back the size of
	// the "chrome" to get the real width...
	if (is_ie(7) && !fIsNonModalDialog)
		nDialogWidth += 6;

	if (fParentIsDialog)
	{
		// For a dialog, window.screenTop is the y-offset of the client area of the dialog and is dynamic, 
		// whereas window.dialogTop is static (so we can't use it since the user may move the dialog)...

		if (is_ie(6))
		{
			var nParentDialogChromeHeight = parseInt(window.dialogHeight) - document.body.clientHeight; // Titlebar and statusbar
			nParentScreenTop -= (nParentDialogChromeHeight / 2);
		}
		
		if (is_ie(7) && !fIsNonModalDialog)
		{
			// See http://blogs.msdn.com/ie/archive/2006/08/25/719355.aspx for further details...
			var nParentDialogTitleBarHeight = 28; // Guesstimate [varies by OS]
			nParentScreenTop -= nParentDialogTitleBarHeight;
		}
	}
	
	var nCenteredDialogLeft = Math.ceil(Math.max(0, nParentScreenLeft + ((nParentBodyWidth - nDialogWidth) / 2)));
	var nCenteredDialogTop = Math.ceil(Math.max(0, nParentScreenTop + ((nParentBodyHeight - nDialogHeight) / 2)));

	sAttribute = ' dialogLeft: ' + nCenteredDialogLeft + 'px; dialogTop: ' + nCenteredDialogTop + 'px; ';

	// alert('window.dialogHeight = ' + window.dialogHeight + '\ndocument.body.clientHeight = ' + document.body.clientHeight + '\nwindow.screenTop = ' + window.screenTop + '\nnParentBodyHeight = ' + nParentBodyHeight + '\nnDialogHeight = ' + nDialogHeight + '\nnCenteredDialogTop = ' + nCenteredDialogTop + '\nnParentScreenTop = ' + nParentScreenTop);
	// sAttribute = ' center:yes; '
	
	return sAttribute;
}


// A function to load the images and sounds used by message_box() to help avoid first-time use download delays...
function initialize_message_box()
{
	// Cache message_box() icons...
	
	cache_image('images/error_icon.gif');
	cache_image('images/warning_icon.gif');
	cache_image('images/question_icon.gif');
	cache_image('images/info_icon.gif');
	
	// Cache message_box() sounds...

	var sound = document.createElement('bgsound');
	sound.id = 'soundCache';
	sound.src = 'sounds/question.wav';
	sound.volume = -10000; // So that we don't hear it
	document.appendChild(sound);
	setTimeout('document.all.soundCache.src = \'sounds/ding.wav\'; setTimeout(\'document.removeChild(document.getElementById("soundCache"))\', 400);', 400);
}


function cache_image(sImagePath)
{
	var imgIcon = document.createElement('img');
	imgIcon.style.position = 'absolute';
	imgIcon.style.top = imgIcon.style.left = 0;
	imgIcon.src = sImagePath;
	imgIcon.style.visibility = 'hidden';
	document.body.appendChild(imgIcon);
	//document.body.removeChild(imgIcon);
}


var g_oPopup = null;

function message_box(sDialogType, sTitle, sMessage, btn1EventHandler, btn2EventHandler)
{
	var disableButton1 = false;
	
	if (sDialogType == 'OKd_CANCEL')
	{
		disableButton1 = true;
		sDialogType = 'OK_CANCEL';
	}
	
	// Check sDialogType...
	if ((sDialogType != 'WAITING_MODAL') &&
	    (sDialogType != 'INFO') && (sDialogType != 'WARNING') && (sDialogType != 'ERROR') &&
	    (sDialogType != 'INFO_MODAL') && (sDialogType != 'WARNING_MODAL') && (sDialogType != 'ERROR_MODAL') &&
	    (sDialogType != 'YES_NO') && (sDialogType != 'OK_CANCEL') &&
	    (sDialogType != 'YES_NO_MODAL') && (sDialogType != 'OK_CANCEL_MODAL'))
	{
		message_box('ERROR', 'MSR ' + g_APP_NAME + ' : Coding Error', 'Invalid DialogType ("' + sDialogType + '") passed to message_box().');
		return;
	}

	//  Button1 Button2 DialogType
	//  ======= ======= ==========
	//  OK      Cancel  OK_CANCEL
	//  Yes     No      YES_NO
	//  --      OK      INFO, WARNING, ERROR

	var eventHandlerName = '';

	if ((sDialogType == 'YES_NO') || (sDialogType == 'OK_CANCEL'))
	{
		// Check btn1EventHandler [required]...
		if ((btn1EventHandler == undefined) || (btn1EventHandler == null) || (btn1EventHandler.length == 0))
		{
			message_box('ERROR', 'MSR ' + g_APP_NAME + ' : Coding Error', 'No btn1EventHandler passed to message_box().');
			return;
		}
		
		if (btn1EventHandler.indexOf('(') == -1)
			btn1EventHandler += '()';
		
		eventHandlerName = btn1EventHandler.substring(0, btn1EventHandler.indexOf('('));

		if (eval('self.' + eventHandlerName) == null)
		{
			message_box('ERROR', 'MSR ' + g_APP_NAME + ' : Coding Error', 'The btn1EventHandler ("' + btn1EventHandler + '") passed to message_box() does not exist.');
			return;
		}
	}
	
	// Check btn2EventHandler [optional]...
	if ((btn2EventHandler != undefined) && (btn2EventHandler != null))
	{
		if (btn2EventHandler.indexOf('(') == -1)
			btn2EventHandler += '()';
		
		eventHandlerName = btn2EventHandler.substring(0, btn2EventHandler.indexOf('('));

		if (eval('self.' + eventHandlerName) == null)
		{
			message_box('ERROR', 'MSR ' + g_APP_NAME + ' : Coding Error', 'The btn2EventHandler ("' + btn2EventHandler + '") passed to message_box() does not exist.');
			return;
		}
		
		btn2EventHandler = 'parent.' + btn2EventHandler + ';';
	}
	else
		btn2EventHandler = '';
	
	// NOTE: We handle INFO, ERROR, and WARNING using the [non-modal] createPopup() function for better control
	//       of formatting than showModalDialog() provides.  If you need the popup to block, use the "_MODAL"
	//       suffix, eg. ERROR_MODAL.
	if ((sDialogType == 'INFO') || (sDialogType == 'ERROR') || (sDialogType == 'WARNING') || (sDialogType == 'YES_NO') || (sDialogType == 'OK_CANCEL'))
	{
		var divPopup = document.createElement('div');
		var soundPopup = document.createElement('bgsound');
		var popupBorderWidth = 2; // px
		var maxPopupWidth = 480;
		var icon = 'images/question_icon.gif';
		var btnPopup1Caption = '';
		var btnPopup2Caption = 'OK';
	
		soundPopup.src = 'sounds/question.wav';
		soundPopup.loop = 1;

		if (sDialogType == 'YES_NO')
		{
			btnPopup1Caption = 'Yes';
			btnPopup2Caption = 'No';
		}
		else
		if (sDialogType == 'OK_CANCEL')
		{
			btnPopup1Caption = 'OK';
			btnPopup2Caption = 'Cancel';
		}
		else
		{
			icon = 'images/' + sDialogType.toLowerCase() + '_icon.gif';
			soundPopup.src = 'sounds/ding.wav';
		}
	    
		// Attributes to allow the div to exceed to size of the parent document (if needed), eg. when the parent is a dialog
		divPopup.style.position = 'absolute';
		divPopup.style.top = divPopup.style.left = 0;
		divPopup.style.width = maxPopupWidth;
		divPopup.style.backgroundColor = '#D4D0C8';

		divPopup.innerHTML = '<table onactivate="oSpaceKeyTarget = (document.all.btnPopup1 != undefined) ? btnPopup1 : btnPopup2;" id="tablePopup" border="0" cellspacing="0" cellpadding="0" style="font: 8pt tahoma;" align="center" onkeydown="if (window.event.keyCode == 27) btnPopup2.click(); if ((window.event.keyCode == 32) || (window.event.keyCode == 13)) oSpaceKeyTarget.click(); if (window.event.keyCode == 39) { btnPopup2.focus(); oSpaceKeyTarget = btnPopup2; } if ((window.event.keyCode == 37) && (document.all.btnPopup1 != undefined)) { btnPopup1.focus(); oSpaceKeyTarget = btnPopup1; }">' +
		                     '  <tr><td colspan="2" style="filter:progid:DXImageTransform.Microsoft.Gradient(GradientType=1, StartColorStr=\'#0a266a\', EndColorStr=\'#a6caf0\'); color:White; padding: 2 0 3 4; border-bottom: solid gray 1px"><b>' + sTitle + '</b><img id="imgPopupCloseDialog" src="images/CloseDialog.bmp" width=16" height="14" style="position: absolute; top: 2;" title="Close" onclick="btnPopup2.click()"></td></tr>' + 
							 '  <tr><td id="tdPopupIcon" style="padding: 12 10 0 8" valign="middle"><img src="' + icon + '" width="38" height="38"><td id="tdPopupMessage" style="padding: 12 12 0 0; font: 9pt tahoma;" valign="middle"></td></tr>' +
							 '	<tr><td colspan="2" align="center" style="padding: 12 0 12 0">' +
							 (((sDialogType == 'YES_NO') || (sDialogType == 'OK_CANCEL')) ? '<button id="btnPopup1" style="width:60;" onclick="parent.g_oPopup.hide(); parent.' + btn1EventHandler + ';" ' + (disableButton1 ? 'disabled' : '') + '>' + btnPopup1Caption + '</button>&nbsp;&nbsp;' : '') +
							 '<button id="btnPopup2" style="width:60;" onclick="parent.g_oPopup.hide(); ' + btn2EventHandler + '">' + btnPopup2Caption + '</button>' +
							 '</td></tr>' +
							 '</table>';

		// Temporarily add it to the document so that we can determine the actual size of the table...
		divPopup.style.visibility = 'hidden';
		document.body.appendChild(divPopup);

		document.all.tdPopupMessage.innerHTML = sMessage.replace(/\n/g, '<br>'); // We do this here [rather than "in-line" in the HTML] so that \n will work

		// Determine the table size [and resize it if it's too big]...
		var actualWidth = document.all.tablePopup.scrollWidth + (popupBorderWidth * 2);
		var actualHeight = document.all.tablePopup.scrollHeight + (popupBorderWidth * 2);
		if (actualWidth > maxPopupWidth)
		{
			document.all.tablePopup.width = maxPopupWidth - (popupBorderWidth * 2); // Note: May lead to some "excess" space on the RHS of the table due to text wrapping
			
			// If the message is pure text (no HTML), then adjust the table width to fit the length of the first line...
			if (document.all.tdPopupMessage.innerHTML.indexOf('<') < 0) // No HTML, just text
				document.all.tablePopup.width = document.all.tdPopupIcon.offsetWidth + get_first_line_width(document.all.tdPopupMessage.innerText, document.all.tdPopupMessage.offsetWidth - 12, '9pt tahoma') + 12; // 12 is the right-hand padding
			
			actualWidth = document.all.tablePopup.scrollWidth + (popupBorderWidth * 2);
			actualHeight = document.all.tablePopup.scrollHeight + (popupBorderWidth * 2);
		}
		document.all.imgPopupCloseDialog.style.left = actualWidth - (popupBorderWidth + document.all.imgPopupCloseDialog.width + 4);
		document.body.removeChild(divPopup);

		// Create the popup...
		g_oPopup = window.createPopup();
		var oPopBody = g_oPopup.document.body;
		oPopBody.style.backgroundColor = '#D4D0C8';
		oPopBody.style.borderLeft = oPopBody.style.borderTop = 'solid LightGrey ' + popupBorderWidth + 'px';
		oPopBody.style.borderRight = oPopBody.style.borderBottom = 'solid Gray ' + popupBorderWidth + 'px';
		oPopBody.innerHTML = divPopup.innerHTML;
		
		// Determine the position of the popup...
		var sLocation = centered_dialog_attribute(actualWidth, actualHeight, true);
		sLocation = sLocation.replace(/px;/g, '').replace(/\sdialog/g, ',').replace(/:\s/g, '=').toLowerCase();
		var nLeft = sLocation.split(',')[1].split('=')[1];
		var nTop = sLocation.split(',')[2].split('=')[1];
		
		// Show it...
		document.appendChild(soundPopup);
		g_oPopup.show(nLeft, nTop, actualWidth, actualHeight);
		document.removeChild(soundPopup);

		return;
	}
		
	// Test cases:
	// var sOutcome = message_box('YesNo', 'Test MessageBox!', 'Some sort of message that we want to display, which can be of arbitrary length, and arbitrary content. And here is another sentence to make it even longer for further testing.');
	// var sOutcome = message_box('OKCancel', 'Test MessageBox!', 'Some\nsort\nof\nmessage.');
	// var sOutcome = message_box('YesNo', 'Test MessageBox!', 'Some sort of message\nthat we want to display, which can be of arbitrary\nlength, and arbitrary content. And here is another sentence to make it even longer for further testing.');

	var MIN_DIALOG_HEIGHT = 148; // 56 (minimum trMessage height) + 40 (trButtons height) + 32 (dialog chrome [XP]) + 20 (status bar)
	var TDMESSAGE_WIDTH = 450;
	var TDICON_WIDTH = 60;
	var AVERAGE_CHAR_WIDTH = 6.3; // A guess at the average 9pt Tahoma character width
	
	var Lines = sMessage.split('\n');
	var iLine = 0;
	var nMessageWidthInPx = 0;
	var nTotalLines = 0;
	
	for (iLine = 0; iLine < Lines.length; iLine++)
	{
		var nLineWidthInPx = Lines[iLine].length * AVERAGE_CHAR_WIDTH;
		var nNumDisplayLinesInLine = Math.max(1, nLineWidthInPx / TDMESSAGE_WIDTH); // Note: the max() is to handle empty lines (eg. "\n\n" case)
		
		nMessageWidthInPx += nLineWidthInPx;
		nTotalLines += Math.ceil(nNumDisplayLinesInLine);
		// alert('nNumDisplayLinesInLine = ' + nNumDisplayLinesInLine + '; nTotalLines = ' + nTotalLines);
	}
	
	nMessageWidthInPx = Math.ceil(nMessageWidthInPx); // Round up fractional pixels
	
	if (Lines.length == 0)
		nMessageWidthInPx = sMessage.Length * AVERAGE_CHAR_WIDTH;

	var nDialogWidth = TDMESSAGE_WIDTH + TDICON_WIDTH;
	var nDialogHeight = Math.max(MIN_DIALOG_HEIGHT, (nTotalLines * 14) + 40 + 20 + 32 + 20); // 14 (height of an 9pt Tahoma character), 40 (the height of trButtons in MessageBox.aspx), 20 (tdMessage top/bottom-padding), 32 (dialog chrome [XP]), 20 (statusbar)
	
	if (nMessageWidthInPx < TDMESSAGE_WIDTH)
		nDialogWidth = nMessageWidthInPx + TDICON_WIDTH + 20; // 20 (tdMessage left/right-padding)
		
	var sOutcome = window.showModalDialog('MessageBox.aspx', sDialogType + '|' + escape(sTitle) + '|' + escape(sMessage), 'dialogWidth: ' + nDialogWidth + 'px; dialogHeight: ' + nDialogHeight + 'px;' + centered_dialog_attribute(nDialogWidth, (is_ie(7) ? (Math.max(150, (nDialogHeight - (32 + 20))) + 32 + 20) : nDialogHeight)) + 'status: yes; scroll: no; help: no;');
	
	return sOutcome;
}


function get_first_line_width(text, available_width, font)
{
	var spanWidthTest = document.createElement('span');
	var words = text.split(' ');
	var line_width = 0;

	spanWidthTest.id = 'spanWidthTest';
	spanWidthTest.style.font = font;

	// Temporarily add the span to the document so that we can determine the actual width of the text...
	document.body.appendChild(spanWidthTest);

	for (var i = 0; i < words.length; i++)
	{
		var prior_text = document.all.spanWidthTest.innerText;
		
		document.all.spanWidthTest.innerText += (i > 0 ? ' ' : '') + words[i];
		
		if (document.all.spanWidthTest.offsetWidth >= available_width)
		{
			document.all.spanWidthTest.innerText = prior_text;
			line_width = document.all.spanWidthTest.offsetWidth;
			break;
		}
	}
	
	// If the line never exceeded available_width, then just return the length of text...
	if (line_width == 0)
		line_width = document.all.spanWidthTest.offsetWidth;

	document.body.removeChild(spanWidthTest);

	return (line_width);
}


function add_option(list, text, value)
{
	var opt = document.createElement('option');
	opt.text = text;
	opt.value = value;
	list.options.add(opt);
}


function delete_array_entry(array, indexToDelete)
{
	var arrayCopy = new Array();

	// Make a copy...
	for (i = 0; i < array.length; i++)
		arrayCopy.push(array[i]);
	
	// Remove all...
	array.length = 0; 
	
	// Add back all but the deleted entry...
	for (i = 0; i < arrayCopy.length; i++)
	{
		if (i != indexToDelete)
			array.push(arrayCopy[i]);
	}
}


function click_on_enter(button)
{
	if (window.event.keyCode == 13)
	{
		if (!button.disabled)
		{
			button.focus();
			button.click();
		}
		return false;
	}
	return true;
}


function close_on_escape(sReturnValue)
{
	if (window.event.keyCode == 27)
	{
		window.returnValue = sReturnValue;
		window.close();
		return false;
	}
	return true;
}


function on_resize_frame()
{
	window.offscreenBuffering = true;
	var nMarginTop = ((document.body.getBoundingClientRect().bottom - (parseInt(document.all.frame.height) + 4)) / 2) * 0.7;
	
	if (nMarginTop <= 2)
		nMarginTop = 0;
		
	document.body.style.marginTop = nMarginTop;
}


function is_ie(version)
{
	return (navigator.userAgent.indexOf('MSIE ' + version) > 0);
}


function check_for_dialog_resize()
{
	// Only required for IE6 since on IE7 window.dialogHeight == document.body.clientHeight
	if (is_ie(6))
	{
		var STATUS_BAR_HEIGHT = 20;
		var nTitleBarHeight = parseInt(window.dialogHeight) - (document.body.clientHeight + STATUS_BAR_HEIGHT); // 25 = "Classic", 32 = "XP"

		if (nTitleBarHeight > 25) // 25 is the dialog titlebar "design-time height"
			window.dialogHeight = parseInt(window.dialogHeight) + (nTitleBarHeight - 25) + 'px';
	}
}


function is_local_file_reference(sFileName)
{
	var fIsLocalFileReference = sFileName.match(/^[acdefgh]:\\(.)+/i);
	// var fIsInUncFormat = sFileName.match(/^\\\\(\w)+\\[acdefgh]\$\\(.)+/i);

	if (fIsLocalFileReference)
	{
		// See http://msdn.microsoft.com/library/default.asp?url=/workshop/essentials/whatsnew/whatsnew_70_sec.asp
		message_box('INFO', 'MSR ' + g_APP_NAME + ' : Browse Image', 'Due to security changes in Internet Explorer 7.0 you must now reference local files using this format:\n\n\\\\(Computer Name)\\(Drive Letter)$\\(Path)\n\nFor example: \\\\MyComputer\\C$\\MyImages\\Image001.jpg');
		return true;
	}
	else
		return false;
}


function draw_resized_image_async(img, sImageFileName, nMaxSize, fnCallbackWhenLoaded) // An alternative to draw_resized_image() if you don't need to wait for the image to load [Note: fnCallbackWhenLoaded is optional]
{
	// Construct a dynamic global variable...
	var nRand = Math.round(Math.random() * 10000);
	var sGlobalVarName = 'g_newImg' + nRand;
	
	window[sGlobalVarName] = document.createElement('img');
	window[sGlobalVarName].onload = function() {on_resized_image_load(img, nMaxSize, sGlobalVarName, fnCallbackWhenLoaded);};
	window[sGlobalVarName].src = sImageFileName;
}

function on_resized_image_load(img, nMaxSize, sGlobalVarName, fnCallbackWhenLoaded) // Only used by draw_resized_image_async()
{
	var nOriginalHeight = window[sGlobalVarName].height;
	var nOriginalWidth = window[sGlobalVarName].width;
	var nHeight = window[sGlobalVarName].height;
	var nWidth = window[sGlobalVarName].width;

	if (nHeight > nMaxSize)
	{
		var dWidthReductionRatio = nMaxSize / nHeight;
		nHeight = nMaxSize;
		nWidth = nWidth * dWidthReductionRatio;
	}

	while (nWidth > nMaxSize)
	{
		var dHeightReductionRatio = nMaxSize / nWidth;
		nWidth = nMaxSize;
		nHeight = nHeight * dHeightReductionRatio;
	}

	img.width = nWidth;
	img.height = nHeight;
	img.src = window[sGlobalVarName].src;

	window[sGlobalVarName] = null;

	if ((fnCallbackWhenLoaded != undefined) && (fnCallbackWhenLoaded != null))
		fnCallbackWhenLoaded(nOriginalHeight, nOriginalWidth);
}


function draw_resized_image(img, sImageFileName, nMaxSize)
{
	var newImg = document.createElement('img');
	
	newImg.src = sImageFileName;

	var nDisplayTimeInMs = 1000;
	while (!newImg.complete)
	{
		message_box('WAITING_MODAL', 'Loading Image', 'Waiting for image to load:' + nDisplayTimeInMs);
		nDisplayTimeInMs += 1000;
	}

	var nHeight = nActualHeight = newImg.height;
	var nWidth = nActualWidth = newImg.width;

	if (nHeight > nMaxSize)
	{
		var dWidthReductionRatio = nMaxSize / nHeight;
		nHeight = nMaxSize;
		nWidth = nWidth * dWidthReductionRatio;
	}

	while (nWidth > nMaxSize)
	{
		var dHeightReductionRatio = nMaxSize / nWidth;
		nWidth = nMaxSize;
		nHeight = nHeight * dHeightReductionRatio;
	}

	img.width = nWidth;
	img.height = nHeight;
	img.src = newImg.src;
	
	return nActualHeight + 'h|' + nActualWidth + 'w';
}


function perform_link_action(sAction, nImageID, nLinkID, sDesktopLinkName, sDesktopLinkURL, sMobileLinkName, sMobileLinkURL, sComments)
{
	var oReq = null;
	var sRet = 'FAIL|';
	var sTitle = 'MSR ' + g_APP_NAME + ' : ' + sAction + ' Link';

	if ((sAction != 'Add') && (sAction != 'Update') && (sAction != 'Delete'))
	{
		message_box('ERROR', sTitle, 'Error: Invalid sAction parameter value \'' + sAction + '\'.');
		return (sRet);
	}

	if (window.XMLHttpRequest)
		oReq = new XMLHttpRequest();
	else
	{
		if (window.ActiveXObject)
			oReq = new ActiveXObject("Microsoft.XMLHTTP");		
	}
	
	if (oReq == null)
	{
		sRet = 'FAIL|Error: Unable to create XMLHttpRequest object.';
	}
	else
	{
		window.status = 'Performing action: ' + sAction + ' Link...'; // Note: Will not display if we're called from a dialog
		document.body.style.cursor = 'wait';

		try
		{
			sUrl = document.URL.substr(0, document.URL.lastIndexOf('/')) + '/PerformLinkAction.aspx?Action=' + escape(sAction) + '&ImageID=' + escape(nImageID) + ((sAction != 'Add') ? '&LinkID=' + escape(nLinkID) : '');
			
			if (sAction != 'Delete')
			{
				sUrl += '&DesktopLinkName=' + escape(sDesktopLinkName) +
						'&DesktopLinkURL=' + escape(sDesktopLinkURL) +
						'&MobileLinkName=' + escape(sMobileLinkName) +
						'&MobileLinkURL=' + escape(sMobileLinkURL) +
						'&Comments=' + escape(sComments);
			}
			
			oReq.open('GET', sUrl, false); // Synchronous
			oReq.send();

			if ((oReq.readyState == 4) && (oReq.status == 200))
				sRet = oReq.responseText;
			else
				sRet = 'FAIL|Error: The action could not be performed (reason: ' + oReq.statusText + ').';
		}
		catch (e)
		{
			var sMsg = (e.description != undefined) ? e.description : e;
			sRet = 'FAIL|Error: The action could not be performed (reason: ' + sMsg + ').';
		}

		document.body.style.cursor = 'default';
		window.status = '';
	}

	var sResultParts = sRet.split('|');
	var sStatus = sResultParts[0];
	
	if (sStatus != 'OK')
	{
		message_box('ERROR', sTitle, sResultParts[1]);
		sRet = 'FAIL|';
	}
	
	return (sRet);
}


function update_vote_count(nImageID, nLinkID, fIncrement)
{
	var oReq = null;
	var sRet = 'FAIL|Error: An unknown failure occurred.';

	if (window.XMLHttpRequest)
		oReq = new XMLHttpRequest();
	else
	{
		if (window.ActiveXObject)
			oReq = new ActiveXObject("Microsoft.XMLHTTP");		
	}
	
	if (oReq == null)
	{
		sRet = 'FAIL|Error: Unable to create XMLHttpRequest object.';
	}
	else
	{
		window.status = 'Recording your rating...'; // Note: Will not display if we're called from a dialog
		document.body.style.cursor = 'wait';

		try
		{
			sUrl = document.URL.substr(0, document.URL.lastIndexOf('/')) + '/UpdateVoteCount.aspx?ImageID=' + escape(nImageID) + '&LinkID=' + escape(nLinkID) + '&Increment=' + fIncrement;
			
			oReq.open('GET', sUrl, false); // Synchronous
			oReq.send();

			if ((oReq.readyState == 4) && (oReq.status == 200))
				sRet = oReq.responseText;
			else
			{
				sRet = 'FAIL|Error: Your rating could not be recorded (reason: ' + oReq.statusText + ').';
			}
		}
		catch (e)
		{
			var sMsg = (e.description != undefined) ? e.description : e;
			sRet = 'FAIL|Error: Your rating could not be recorded (reason: ' + sMsg + ').';
		}

		document.body.style.cursor = 'default';
		window.status = '';
	}

	var sResultParts = sRet.split('|');
	var sStatus = sResultParts[0];
	
	if (sStatus != 'NO_POPUP')
		message_box(((sStatus == 'OK') ? 'INFO' : 'ERROR'), 'MSR ' + g_APP_NAME + ' : Record Rating', sResultParts[1]);
	
	return (sStatus);
}



function get_url_response(sUrl)
{
	var sRet = null;
	var oReq = null;
	var fError = false;

	if (window.XMLHttpRequest)
		oReq = new XMLHttpRequest();
	else
	{
		if (window.ActiveXObject)
			oReq = new ActiveXObject("Microsoft.XMLHTTP");		
	}
	
	if (oReq == null)
	{
		sRet = 'Error: Unable to create XMLHttpRequest object.';
		fError = true;
	}
	else
	{
		try
		{
			oReq.open('GET', sUrl, false); // Synchronous
			oReq.send();

			if ((oReq.readyState == 4) && (oReq.status == 200))
			{
				sRet = oReq.responseText;
			}
			else
			{
				sRet = 'Error: Unable to get page response (reason: ' + oReq.status + ' - ' + oReq.statusText + ').';
				fError = true;
			}
		}
		catch (e)
		{
			var sMsg = (e.description != undefined) ? e.description : e;
			sRet = 'Error: Unable to get page response (reason: ' + sMsg + ').';
			fError = true;
		}
	}

	if (fError)
	{
		message_box('ERROR', 'MSR ' + g_APP_NAME + ' : Get Page Response', sRet);
		sRet = null;
	}
	
	return (sRet);
}


// [SECTION] Functions common to both AddImage and MyImages (and AddSecondaryLink)...

function on_link_url_changed(sLinkType)
{
	var	oTxtCntrl = eval('document.all.txt' + sLinkType + 'LinkURL');
	var sURL = trim(oTxtCntrl.value.toLowerCase());
	var sHTTP = 'http';
	
	if ((sURL.length > 0) &&            // URL hasn't been completely erases 
	    !(sHTTP.indexOf(sURL) == 0) &&  // User isn't typing "http" themself
	    !(sURL.indexOf(sHTTP, 0) == 0)) // URL doesn't start with "http"
	{
		oTxtCntrl.value = sHTTP + '://' + trim(oTxtCntrl.value);
		sURL = oTxtCntrl.value.toLowerCase(); // In preparation for subsequent checks
	}

	if (((sURL.indexOf('http://', 0) == 0)  && (sURL.length > 7)) || 
	    ((sURL.indexOf('https://', 0) == 0) && (sURL.length > 8)))
		eval('document.all.btnView' + sLinkType.replace(/Primary/, '') + 'Link.disabled = false');
	else
		eval('document.all.btnView' + sLinkType.replace(/Primary/, '') + 'Link.disabled = true');

	return true;
}


function on_secondary_links_allowed_clicked()
{
	var fState = document.all.chkSecondaryLinksAllowed.checked;
	
	document.all.txtSecondaryLinkLimit.disabled = !fState;
	document.all.txtSecondaryLinkLimit.style.backgroundColor = fState ? 'White' : 'Gainsboro';
	document.all.txtSecondaryLinkLimit.value = fState ? '1' : '0';
}


function verify_image_attribute_lengths(txtaComments, txtDesktopLinkName, txtDesktopLinkURL, txtMobileLinkName, txtMobileLinkURL)
{
	var sError = '';

	txtaComments.value = trim(txtaComments.value);
	txtDesktopLinkName.value = trim(txtDesktopLinkName.value);
	txtDesktopLinkURL.value = trim(txtDesktopLinkURL.value);
	txtMobileLinkName.value = trim(txtMobileLinkName.value);
	txtMobileLinkURL.value = trim(txtMobileLinkURL.value);

	var nCommentsLength = txtaComments.value.length;
	var nDesktopLinkNameLength = txtDesktopLinkName.value.length;
	var nDesktopLinkURLLength = txtDesktopLinkURL.value.length;
	var nMobileLinkNameLength = txtMobileLinkName.value.length;
	var nMobileLinkURLLength = txtMobileLinkURL.value.length;
	
	if (nCommentsLength == 0)
	{
		sError = 'Comments is a required field and so cannot be left empty.';
		txtaComments.focus();
	}
	else
	if (((nDesktopLinkNameLength == 0) && (nDesktopLinkURLLength != 0)) ||
		((nDesktopLinkNameLength != 0) && (nDesktopLinkURLLength == 0)))
	{
		sError = 'Desktop Link Name / URL must be supplied as a pair or not at all.'
		txtDesktopLinkName.focus();
	}
	else
	if (((nMobileLinkNameLength == 0) && (nMobileLinkURLLength != 0)) ||
		((nMobileLinkNameLength != 0) && (nMobileLinkURLLength == 0)))
	{
		sError = 'Mobile Link Name / URL must be supplied as a pair or not at all.'
		txtMobileLinkName.focus();
	}
	else
	if ((nDesktopLinkNameLength > 0) && (nDesktopLinkNameLength < 5))
	{
		sError = 'Desktop Link Name must have at least 5 characters.';
		txtDesktopLinkName.focus();
	}
	else
	if ((nDesktopLinkURLLength > 0) && (nDesktopLinkURLLength < 8))
	{
		sError = 'Desktop Link URL must have at least 8 characters.';
		txtDesktopLinkURL.focus();
	}
	else
	if ((nMobileLinkNameLength > 0) && (nMobileLinkNameLength < 5))
	{
		sError = 'Mobile Link Name must have at least 5 characters.';
		txtMobileLinkName.focus();
	}
	else
	if ((nMobileLinkURLLength > 0) && (nMobileLinkURLLength < 8))
	{
		sError = 'Mobile Link URL must have at least 8 characters.';
		txtMobileLinkURL.focus();
	}

	if (sError.length == 0)
	{
		var sDesktopURL = txtDesktopLinkURL.value.toLowerCase();
		var sMobileURL = txtMobileLinkURL.value.toLowerCase();

		if ((sDesktopURL.length > 0) &&
			!(((sDesktopURL.indexOf('http://', 0) == 0)  && (sDesktopURL.length > 7)) || 
			  ((sDesktopURL.indexOf('https://', 0) == 0) && (sDesktopURL.length > 8))))
		{
			sError = 'Desktop Link URL must begin with \'http://\' or \'https://\'.';
			txtDesktopLinkURL.focus();
		}
		else
		if ((sMobileURL.length > 0) &&
			!(((sMobileURL.indexOf('http://', 0) == 0)  && (sMobileURL.length > 7)) || 
			  ((sMobileURL.indexOf('https://', 0) == 0) && (sMobileURL.length > 8))))
		{
			sError = 'Mobile Link URL must begin with \'http://\' or \'https://\'.';
			txtMobileLinkURL.focus();
		}
	}
		
	return sError;
}


function verify_image_details(sMode, nLinkLimitMinimum, fDoDesktopURLCheck, fDoMobileURLCheck)
{
	var sError = '';
	
	document.all.txtImageName.value = trim(document.all.txtImageName.value);
	
	var nImageNameLength = document.all.txtImageName.value.length;

	// Validate user-entered data...
	if (nImageNameLength < 5)
	{
		sError = 'Image Name must have at least 5 characters.';
		document.all.txtImageName.focus();
	}
	else
		sError = verify_image_attribute_lengths(document.all.txtaPrimaryComments,
		                                        document.all.txtPrimaryDesktopLinkName,
		                                        document.all.txtPrimaryDesktopLinkURL,
		                                        document.all.txtPrimaryMobileLinkName,
		                                        document.all.txtPrimaryMobileLinkURL);

	if (sError.length == 0)
	{
		// Check for potentially malicious input...
		var pattern = /<\w+/;

		if (document.all.txtImageName.value.match(pattern))
		{
			sError = 'Image Name';
			document.all.txtImageName.focus();
		}
		if (document.all.txtaPrimaryComments.value.match(pattern))
		{
			sError = 'Comments';
			document.all.txtaPrimaryComments.focus();
		}
		if (document.all.txtPrimaryDesktopLinkName.value.match(pattern))
		{
			sError = 'Desktop Link Name';
			document.all.txtPrimaryDesktopLinkName.focus();
		}
		if (document.all.txtPrimaryMobileLinkName.value.match(pattern))
		{
			sError = 'Mobile Link Name';
			document.all.txtPrimaryMobileLinkName.focus();
		}
			
		if (sError.length > 0)
			sError = sError + ' contains potentially malicious input.\nTry removing the \'<\' character.';
	}	

	if (sError == '')	
	{
		if (document.all.chkSecondaryLinksAllowed.checked || (sMode == 'UPDATE'))
		{
			var nLinkLimit = document.all.txtSecondaryLinkLimit.value;

			if ((nLinkLimit < nLinkLimitMinimum) || (nLinkLimit > 64))
			{
				sError = 'The User Link Limit must be between ' + nLinkLimitMinimum + ' and 64.';
				document.all.chkSecondaryLinksAllowed.checked = true;
				on_secondary_links_allowed_clicked();
				document.all.txtSecondaryLinkLimit.value = (nLinkLimit < nLinkLimitMinimum) ? nLinkLimitMinimum : 64;
				document.all.txtSecondaryLinkLimit.focus();
			}
		}
	}

	if ((sError == '') && (document.all.txtPrimaryDesktopLinkURL.value.length > 0) && fDoDesktopURLCheck)
		sError = verify_link('Desktop', document.all.txtPrimaryDesktopLinkURL);

	if ((sError == '') && (document.all.txtPrimaryMobileLinkURL.value.length > 0) && fDoMobileURLCheck)
		sError = verify_link('Mobile', document.all.txtPrimaryMobileLinkURL);

	if (sError != '')
	{
		message_box('ERROR', top.document.title, sError);
		return false;
	}
	else
	{
		return true;
	}
}


function verify_link(sLinkType, txtLinkURL)
{
	var sError = '';
	
	if (document.all.btnSubmit != null)
		document.all.btnSubmit.disabled = false;
	
	eval('document.all.btnView' + sLinkType + 'Link.disabled = true');
	
	document.body.style.cursor = 'wait';

	var sHttpStatus = verify_url(sLinkType, txtLinkURL.value);
	
	if (sHttpStatus != 'OK')
	{
		var fValidSyntax = verify_url_syntax(trim(txtLinkURL.value));

		if (!fValidSyntax)
			sError = 'The ' + sLinkType + ' Link URL specified has an invalid format.'
		else
			sError = 'The ' + sLinkType + ' Link URL specified could not be resolved (reason: ' + sHttpStatus + ').\n\nTip: Check that the URL exists by clicking the "View..." button.';

		txtLinkURL.focus();
	}

	if (document.all.btnSubmit != null)
		document.all.btnSubmit.disabled = false;

	eval('document.all.btnView' + sLinkType + 'Link.disabled = false');
	document.body.style.cursor = 'default';
	
	return sError;
}


var MAX_COMMENT_LENGTH = 500;


function on_comments_keypress(txtaComments)
{
	if (txtaComments.value.length >= MAX_COMMENT_LENGTH)
	{
		document.all.spnCommentCharLimit.style.fontWeight = 'bold';
		document.all.sound.src = 'sounds/ding.wav';
		return false;
	}
	else
		return true;
}


function on_comments_keyup(txtaComments) // Since keypress doesn't receive all keystrokes we have to do some processing here too
{
	if (txtaComments.value.length < MAX_COMMENT_LENGTH)
		document.all.spnCommentCharLimit.style.fontWeight = 'normal';
	
	if (txtaComments.value.length > MAX_COMMENT_LENGTH) // Eg. after a paste operation
	{
		txtaComments.value = txtaComments.value.substring(0, MAX_COMMENT_LENGTH);
		document.all.spnCommentCharLimit.style.fontWeight = 'bold';
		document.all.sound.src = 'sounds/ding.wav';
	}
}


function on_view_click(sLinkType, sURL)
{
	var wnd = null;
	var sWndName = 'UpLinkView' + sLinkType + 'LinkWindow';
	
	if (sLinkType == 'Mobile')
	{
		/*
		var nNewWindowWidth = 240;
		var nNewWindowHeight = 320;
		
		var sLocation = centered_dialog_attribute(nNewWindowWidth + 10, nNewWindowHeight + 25); // 10 is the combined width of IEs left/right window border; 25 is the titlebar minimum height [since nNewWindowHeight will only be the content area height of the new window]
		sLocation = sLocation.replace(/px;/g, '').replace(/\sdialog/g, ',').replace(/:\s/g, '=').toLowerCase();
		
		wnd = window.open(sURL, sWndName, 'height=' + nNewWindowHeight + ', width=' + nNewWindowWidth + sLocation);
		*/
		
		var nNewWindowWidth = 309;
		var nNewWindowHeight = 553;
		
		var sLocation = centered_dialog_attribute(nNewWindowWidth + 10, nNewWindowHeight + 25); // 10 is the combined width of IEs left/right window border; 25 is the titlebar minimum height [since nNewWindowHeight will only be the content area height of the new window]
		sLocation = sLocation.replace(/px;/g, '').replace(/\sdialog/g, ',').replace(/:\s/g, '=').toLowerCase();
		
		wnd = window.open('ViewMobileLink.aspx?ImpersonatePocketIE=1&URL=' + escape(sURL), sWndName, 'height=' + nNewWindowHeight + ', width=' + nNewWindowWidth + sLocation);
	}

	if (sLinkType == 'Desktop')
		wnd = window.open(sURL, sWndName);
		
	if (wnd != null)
		wnd.focus();
}
// [END SECTION]