/**
 * @author Christopher Keefer 2011
 * @desc This script describes the common functions for all the pages throughout the site.
 */

$(document).ready(function(){
	// Attach behaviours on DOM ready
	callLogin();
});

/**
 * @desc Don't forget to change this variable's value depending on server setup!
 */
var hostLoc = "http://" + location.hostname +"/";
var siteName = "Sane Method";
/**
 *  @desc 1=su, 2=google, 3=yahoo, 4=facebook, 5=twitter
 */
var loginProvider = 1;

/**
 * @desc Calls the creation of a login and register dialog, which also allows the user to login/register with Google,
 * Yahoo, Facebook or Twitter. Dynamically created so it can exist easily and instantly on any given page. Attached
 * with live so newly added links gain the behaviours.
 */
function callLogin(){
	$('.callLogin').click(function(event){
		event.preventDefault();
		
		// Insert a new div into the DOM before the footer, with all our login form elements inside, if it
		// doesn't already exist in the DOM tree
		if (!($('#loginOverlay').length)){
			makeLoginOverlay();
		}
		
		/* The overlay elements have been added to the DOM, or determined to already exist in the DOM,
		 * so show them to the user. */
		
		$('#overlayMask').fadeIn(200,function(){
			$('#loginOverlay').animate({
				'top':'160px'}, 300);
		});
		
		// Attach the hiding of the form and overlay mask to the
		// (x) cancel button in the corner, and the login cancel button
		$('#overlayCloseButton, #loginCancel').click(function(event){
			event.preventDefault();
			$('#loginOverlay').animate({
				'top':'-550px'},300,function(){
					$('#overlayMask').fadeOut(300);
				});
			});
		
		// Attach behaviours to the login and register tabs
		$('#loginTabLink').click(function(event){
			event.preventDefault();
			$('#registerFormDiv').fadeOut(200, function(){
				$('#loginFormDiv').fadeIn(200);
			});
			$('#registerTabLink').removeAttr('class').attr('class','tabLink');
			$('#loginTabLink').removeAttr('class').attr('class','tabLink activeTabLink');
		});
		
		$('#registerTabLink').click(function(event){
			event.preventDefault();
			$('#loginFormDiv').fadeOut(200, function(){
				$('#registerFormDiv').fadeIn(200);
			});
			$('#loginTabLink').removeAttr('class').attr('class','tabLink');
			$('#registerTabLink').removeAttr('class').attr('class','tabLink activeTabLink');
		});
		
		validateRegistration(); // Handle all the data processing of the registration form
		doInternalLogin(); // Handle all the data processing for the login that doesn't involve external sources
		
		 // Attach proper behaviours to each of the provider buttons
		// First, siteName button
		$('#providerSU').click(function(event){
			$('#providerGoogle,#providerYahoo,#providerFacebook,#providerTwitter').removeAttr('class');
			$('#providerSU').attr('class','activePImage');
			$('#userNameField,#passwordField,#loginSubmit,#loginCancel').removeAttr('disabled').fadeIn();
			$('#loginText').text('Log in to your '+siteName+' account');
			$('#userNameField').focus();
			loginProvider = 1;
		});
		
		// Provider Google button
		$('#providerGoogle').click(function(event){
			$('#userNameField,#passwordField,#loginSubmit').attr('disabled','disabled');
			$('#providerSU,#providerYahoo,#providerFacebook,#providerTwitter').removeAttr('class');
			$('#providerGoogle').attr('class','activePImage');
			$('#loginText').attr('class','wait').text('Please wait while we redirect you to Google...');
			loginProvider = 2;
			
			// Ajax up our google OpenID request, and send this window over there.
			$.ajax({ // jQuery Ajax time...
				url: hostLoc + "Users/login/google/1", // provider=google, step=1
				type: "POST",
				data: "location="+encodeURIComponent(window.location.href), // Send our current location so we can head back here later
				dataType: "text", // plain text response expected
				success: function(data){
					// Set the previously opened windows location to our authorization url
		            window.location.href = ""+data;
				},
				error: function(jqxhr,textStatus,errorThrown){
					$('#loginText').attr('class','error').text('Error opening a window to Google...'+
							' Please consider trying again later, signing on with a different provider'+
							' or registering for an account with us.');
				}
			});
			
		});
		
		// Provider Yahoo button
		$('#providerYahoo').click(function(event){
			$('#userNameField,#passwordField,#loginSubmit').attr('disabled','disabled');
			$('#providerSU,#providerGoogle,#providerFacebook,#providerTwitter').removeAttr('class');
			$('#providerYahoo').attr('class','activePImage');
			$('#loginText').attr('class','wait').text('Please wait while we redirect you to Yahoo!...');
			loginProvider = 2;
			
			// Ajax up our google OpenID request, and send this window over there.
			$.ajax({ // jQuery Ajax time...
				url: hostLoc + "Users/login/Yahoo/1", // provider=google, step=1
				type: "POST",
				data: "location="+encodeURIComponent(window.location.href), // Send our current location so we can head back here later
				dataType: "text", // plain text response expected
				success: function(data){
					// Set the previously opened windows location to our authorization url
		            window.location.href = ""+data;
				},
				error: function(jqxhr,textStatus,errorThrown){
					$('#loginText').attr('class','error').text('Error opening a window to Google...'+
							' Please consider trying again later, signing on with a different provider'+
							' or registering for an account with us.');
				}
			});
			
		});
		
		// Provider Facebook button
		$('#providerFacebook').click(function(event){
			$('#userNameField,#passwordField,#loginSubmit').attr('disabled','disabled');
			$('#providerSU,#providerGoogle,#providerYahoo,#providerTwitter').removeAttr('class');
			$('#providerFacebook').attr('class','activePImage');
			$('#loginText').attr('class','wait').text('Please wait while we redirect you to Facebook...');
			loginProvider = 4;
			
			// Ajax into our OAuth request, and send this window in that direction
			$.ajax({
				url: hostLoc + "Users/login/Facebook/1",
				type: "POST",
				data: "location="+encodeURIComponent(window.location.href),
				dataType: "text",
				success: function(data){
				window.location.href = ""+data;
				},
				error: function(jqxhr, textStatus, errorThrown){
					$('#loginText').attr('class','error').text('Error opening a window to Facebook...'+
					' Please consider trying again later, signing on with a different provider'+
					' or registering for an account with us.');
				}
			});
		});
		
		// Provider Twitter button
		$('#providerTwitter').click(function(event){
			$('#userNameField,#passwordField,#loginSubmit').attr('disabled','disabled');
			$('#providerSU,#providerGoogle,#providerYahoo,#providerFacebook').removeAttr('class');
			$('#providerTwitter').attr('class','activePImage');
			$('#loginText').attr('class','wait').text('Please wait while we redirect you to Twitter...');
			loginProvider = 5;
			
			// Ajax into our OAuth request, and send this window in that direction
			$.ajax({
				url: hostLoc + "Users/login/twitter/1",
				type: "POST",
				data: "location="+encodeURIComponent(window.location.href),
				dataType: "text",
				success: function(data){
				window.location.href = ""+data;
				},
				error: function(jqxhr, textStatus, errorThrown){
					$('#loginText').attr('class','error').text('Error opening a window to Facebook...'+
					' Please consider trying again later, signing on with a different provider'+
					' or registering for an account with us.');
				}
			});
		});
		
		
		
		
		/*
		 * DON'T FIXME: Why not? Because this doesn't work right. If the user presses the esc key twice while
		 * the elements in question exist, the div will drop into place, but the overlay will fade in and 
		 * immediately fade back out. Can this be fixed? Probably. But we've given a plethora of other closing
		 * options... we can do without this one, I think.
		// Also bind the hiding of the form and overlay mask to the ESC key press anywhere in the document
		$(document).bind("keydown",function(event){
			if (event.which == 27){ // the ESCAPE key
				event.preventDefault();
				$('#loginOverlay').animate({
					'top':'-400px'},300,function(){
						$('#overlayMask').fadeOut(300);
					});
				}
			});
			*/
		
		});
}

/**
 * @desc Moved outside of callLogin for readability. This simply involves capturing the submit event from the register
 * form, and then doing all the ginpokery that validation involves, including:
 * 1- Checking that none of the fields are blank and have the requisite number of characters in them;
 * 2- Checking that none of the fields have invalid characters in them (regular expressions... and now we have 2 problems...)
 * 3- AJAX calls to determine name and email collision.
 * At each stage of the complex web of if statements, we set the relevant popup to display our error message, and
 * bring the problematic field back into focus. The popup will restore its html to its original form upon dissapearing.
 * @see saveOldHtml(element, newHtml) and restoreOldHtml(element) 
 */
function validateRegistration(){
	// Attach behaviour to the register button on the registerFormDiv
	$('#registerForm').submit(function(event){
		event.preventDefault();
		// check to see that the register fields aren't blank, and otherwise
		// AJAX the register request to the server, where the fields not
		// part of the actual submission form (ie.$autoConfirm, 
		// $member, $expireDate, $permissions)
		var rgName = $('#registerNameField').val(),
		rgEmail = $('#registerEmailField').val(),
		rgPass = $('#registerPasswordField').val(),
		rgCon = $('#confirmPasswordField').val();
		// We need to check that all the fields are filled in accurately, and
		// highlight them if not, as well as append an error message to the top
		// text indicating the problem
		if (rgName == null || rgName == "" || rgName.length < 3 || rgName.length >= 50){
			// Problem with user name - either blank or not enough or too many characters
			saveOldHtml('#registerNameField + .popup','<p class="error">Invalid user name - it needs to be'+
					' at least 3 and less than 50 characters long.', false);
			$('#registerNameField').trigger('focus');
			return;
		}else{ // Name has requisite number of characters - are they the right kind, though?
			var reg = /[^a-z0-9_-]/i;
			if (reg.test(rgName)){
				saveOldHtml('#registerNameField + .popup','<p class="error">Invalid characters in user name -'+
				' valid characters are a-z, 0-9, underscore and hyphen.</p>', false);
				$('#registerNameField').trigger('focus');
				return;
			}
		}
		if (rgEmail == null || rgEmail == "" || rgEmail.length < 6){
			// Problem with email - either blank or not enough characters (minimum of 6 - consider
			// the @, the . and the extension have a minimum of 4 characters, and then at least one
			// character for the domain and one character for the username)
			saveOldHtml('#registerEmailField + .popup','<p class="error">Invalid email address, please try another.</p>', 
					false);
			$('#registerEmailField').trigger('focus');
			return;
		}
		if (rgPass == null || rgPass == "" || rgPass.length < 12 || rgPass.length > 80){ // Password is non-existant or too short, or way too long
			saveOldHtml('#registerPasswordField + .popup','<p class="error">Invalid password, it needs to be'+
					' at least 12 characters long.</p>', false);
			$('#registerPasswordField').trigger('focus');
			return;
		}
		if (rgCon == null || rgCon == "" || rgCon != rgPass){ // Confirmation password is non-existant or doesn't match
			saveOldHtml('#confirmPasswordField + .popup','<p class="error">Your passwords do not match,'+
			' please try again. Remember that case matters!</p>', false);
			$('#confirmPasswordField').trigger('focus');
			return;
		}
		
		// If we've managed to make it this far, then all the fields are filled in adequately - send the user name
		// and email fields off to the server via AJAX to determine whether they're already in use.
		
		// First, popup a dialog to occupy their eyes while we AJAX away... 
		saveOldHtml('#registerPasswordField + .popup','<p class="wait">Sending Now, Please Wait...</p>', 
				false);
		$('#registerPasswordField + .popup').triggerHandler('mouseenter');
		// Disable the register button so they don't hit it a billion times...
		$('#registerSubmit').attr('disabled','disabled');
				
		$.ajax({ // jQuery Ajax time...
			url: hostLoc + "Users/collision_check/"+encodeURIComponent(rgName)+"/"+encodeURIComponent(rgEmail),
			type: "POST",
			data: "null=null", // We don't really need to send anything on this step, the info is in the URI
			dataType: "text", // plain text response expected
			success: function(data){
				var response = parseInt(data);
				switch(response){
				case -1: // UserName collided
					$('#registerSubmit').removeAttr('disabled');
					$('#registerPasswordField + .popup').triggerHandler('mouseleave'); // Hide the 'Wait for Ajax' dialog
					saveOldHtml('#registerNameField + .popup','<p class="error">Sorry, that user name is already'+
							' taken. Please try another.</p>', false);
					$('#registerNameField').trigger('focus');
					return;
					break;
				case -2: // email collided
					$('#registerSubmit').removeAttr('disabled');
					$('#registerPasswordField + .popup').triggerHandler('mouseleave'); // Hide the 'Wait for Ajax' dialog
					saveOldHtml('#registerEmailField + .popup','<p class="error">Sorry, that email address is'+
					" already in use. Please enter another. If you've forgotten your password, please click on the"+
					" 'Forgot Your Password?' link on the log in page to have it sent to your email address.</p>", 
					false);
					$('#registerEmailField').trigger('focus');
					return;
					break;
				case 1: // all is well - register!
					doRegistration(rgName,rgEmail,rgPass);
					break;
				}
			},
			error: function(jqxhr,textStatus,errorThrown){
				saveOldHtml('#registerNameField + .popup','<h4 class="error">Internal Error - Check Collision</h4>'+
				'<p>Sorry '+rgName+" the registration process failed, probably due to server error."+
				' Please try again later, or try logging in with one of our other login service providers.</p>', 
				false);
				$('#registerNameField').triggerHandler('focus');
			}
		});
	});
}

/**
 * Responsible for the Ajax portion of the registration process - removed from the validateRegistration() function
 * to increase readability and do some bug-squashing.
 * @param rgName
 * @param rgEmail
 * @param rgPass
 * @return
 */
function doRegistration(rgName, rgEmail, rgPass){
	$.ajax({ // jQuery Ajax time...
		url: hostLoc + "Users/register/",
		type: "POST",
		data: "userName="+encodeURIComponent(rgName)+"&email="+encodeURIComponent(rgEmail)+"&password="+encodeURIComponent(rgPass), // This time we'll send the data via post for a little added security
		dataType: "text", // plain text response expected
		success: function(data){
			$('#registerSubmit').removeAttr('disabled');
			$('#registerPasswordField').triggerHandler('blur'); // Hide the 'Wait for Ajax' dialog
			var regRes = parseInt(data);
			switch(regRes){
			case 0: // Registration process failed due to internal problems (MySQL failure?)
				saveOldHtml('#registerNameField + .popup','<h4 class="error">Internal Server Error</h4>'+
				'<p>Sorry '+rgName+" the registration process failed, probably due to server error."+
				' Please try again later, or try logging in with one of our other login service providers.</p>', 
				false);
				$('#registerNameField').triggerHandler('focus');
				break;
			case 1: // Succesfully registered
				// show a big popup with a link to the confirm page in it confirming registration success
				saveOldHtml('#registerNameField + .popup','<h4 class="success">Registration Successful</h4>'+
				'<p>Alright '+rgName+" you're successfully registered. An e-mail will be sent to"+
				' the e-mail address you signed up with. Please follow the link in the e-mail to confirm your'+
				' account. Or, if your having trouble with the link, just copy the confirmation number in the e-mail'+
				' and go to the directed url or just <a href="'+hostLoc+"Users/confirm"+'">click here</a> to'+
				' enter your number and finish the registration process.</p>', false);
				$('#registerNameField').triggerHandler('focus');
				break;
			case -2: // UserName collision
				saveOldHtml('#registerNameField + .popup','<p class="error">Sorry, that user name is already'+
				' taken. Please try another.</p>', false);
				$('#registerNameField').trigger('focus');
				break;
			case -3:
				saveOldHtml('#registerEmailField + .popup','<p class="error">Sorry, that email address is'+
				" already in use. Please enter another. If you've forgotten your password, please click on the"+
				" 'Forgot Your Password?' link on the log in page to have it sent to your email address.</p>", 
				false);
				$('#registerEmailField').trigger('focus');
				break;
			case -4:
				saveOldHtml('#registerNameField + .popup','<p class="error">Sorry, the captcha input was incorrect.'+
						' Please try again.</p>', false);
				$('#registerNameField').triggerHandler('focus');
				$('#recaptcha_response_field').trigger('focus');
				break;
			case -1: // For both, we're currently delaying - too many register requests from this ip
			default:
				saveOldHtml('#registerNameField + .popup','<h4 class="error">Too Many Registration Attempts</h4>'+
				'Sorry, your ip address has made too many registration attempts in the last hour. We block'+
				" attempts like this to prevent mass bot-account signup. Please try again in an hour, or "+
				" login with one of our other login service providers below.</p>", 
				false);
				$('#registerNameField').triggerHandler('focus');
				break;
			}
		},
		error: function(jqxhr, textStatus, errorThrown){
			saveOldHtml('#registerNameField + .popup','<h4 class="error">Internal Javascript Error</h4>'+
			'<p>Sorry '+rgName+" the registration process failed, probably due to server error."+
			' Please try again later, or try logging in with one of our other login service providers.</p>', 
			false);
			$('#registerNameField').triggerHandler('focus');
		}
	});
}

/**
 * @desc Made external to callLogin for readability's sake. doInternalLogin does all the processing involved
 * in loggin a user into their account and communicating via AJAX with the server-side scripts
 * involved, as well as doing some of the first steps involved in logging a user into their Yahoo, Facebook
 * or Twitter accounts. The next steps, and the entirety of the Google process, happen in finishExtLogin.
 * @see <code>finishExtLogin()</code>
 */
function doInternalLogin(){
	$('#loginSubmit').click(function(event){
		event.preventDefault();
		if (loginProvider == 1){ // login provider is set to this site [internal]
			// do simple form validation
			var lgName = $('#userNameField').val(),
			lgPass = $('#passwordField').val();
			var userPop = '#userNameField + .popup',
			passPop = '#passwordField + .popup';
			
			if (lgName == null || lgName == ""){
				saveOldHtml('#userNameField + .popup','<p class="error">Please enter a user name to login with.</p>', 
				false);
				$('#userNameField').trigger('focus');
				return;
			}else if (lgPass == null || lgPass == "" || lgPass.length < 12){ // <12 because we require at least 12 characters during registration
				saveOldHtml('#passwordField + .popup','<p class="error">Please enter your full password to login.</p>', 
				false);
				$('#passwordField').trigger('focus');
				return;
			}
			var reg = /[^a-z0-9_-]/i;
			if (reg.test(lgName)){
				saveOldHtml('#userNameField + .popup','<p class="error">Invalid characters in user name -'+
				' valid characters are a-z, 0-9, underscore and hyphen.</p>', false);
				$('#userNameField').trigger('focus');
				return;
			}
			event.preventDefault();
			// Clear existing popups
			/*
			$('.bubbleInfo').each(function(){
				$('.trigger', this).triggerHandler('clearPopup');
			});
			*/
			// We're good to go - send the username and password to the server via AJAX to begin the authentication
			saveOldHtml('#userNameField + .popup','<p class="wait">Logging You In, Please Wait...</p>', 
					false);
			$('#userNameField + .popup').triggerHandler('displayPopup');
			// Disable the register button so they don't hit it a billion times...
			$('#loginSubmit').attr('disabled','disabled');
			
			
			
			$.ajax({ // jQuery Ajax time...
				url: hostLoc + "Users/login/", // provider=su, step=1, by DEFAULT
				type: "POST",
				data: "userName="+encodeURIComponent(lgName)+"&password="+encodeURIComponent(lgPass),
				dataType: "text", // plain text response expected
				success: function(data){
				$('#loginSubmit').removeAttr('disabled');
					var response = parseInt(data);
					switch(response){
					case -4: // User is banned
						saveOldHtml('#userNameField + .popup','<p class="error">This user account is'+
								" permanently banned from "+siteName+".</p>", 
						false);
						$('#userNameField').trigger('focus');
						break;
					case -2: // Password/Username combo not a match or doesn't exist
						saveOldHtml('#userNameField + .popup','<p class="error">Sorry, that user name and'+
								" password combination is incorrect or doesn't exist. Please try again.</p>", 
						false);
						$('#userNameField').trigger('focus');
						break;
					case -3: // user isn't confirmed yet
						saveOldHtml('#userNameField + .popup','<p class="error">Sorry, your user account'+
								" isn't confirmed yet. <a href='"+hostLoc+"Users/confirm/'>Click Here</a> to"+
								" confirm your account, or get a new confirmation e-mail sent to you.</p>", 
						false);
						$('#userNameField').trigger('focus');
						break;
					case 1: // A-Okay - Sessions variables are set, just reload the page
						window.location.replace(window.location.href);
						break;
					default: // An error occurred - just display it for now
						saveOldHtml('#userNameField + .popup','<p class="error">'+data+"</p>", 
						false);
						$('#userNameField').trigger('focus');
						//var delay = setTimeout("$('#loginSubmit').trigger('click')",(response*1000));
						break;
					}
				},
				error: function(jqxhr,textStatus,errorThrown){
					jAlert('Error sending login information to server:'+
					textStatus+" -- "+errorThrown);
				}
			});
		}
	});
}

/**
 * Responsible for the DOM creation of the login overlay mask and dialog - outset from the callLogin() function
 * for readability's sake
 * @return
 */
function makeLoginOverlay(){
	
	$('body').append($('<div></div>'). // Add an empty div of class 'overlayMask', which is just that
			attr({
				class:'overlayMask',
				id:'overlayMask',
				display:'none'
			})
		);
	$('body').append($('<div></div>'). // Add the div containing all of the form elements necessary to
			attr({						// log in or register
				id:'loginOverlay',
				class:'overlayBox'
				}). 
		append($('<a></a>').	// Add the close button on the upper-right hand corner (see CSS a.overlayBoxClose)
			attr({
				class:'overlayBoxClose',
				id:'overlayCloseButton',
				href:'#'
			})	
		).
		append($('<div></div>').	// Containing div for the tabs, to prevent the negative margin float
			attr({					// overlap problem
				id:'overlayTabDiv',
				class:'tabDiv'
			}).
		append($('<a></a>').	// Add the login tab link and display it as active
			attr({
				id:'loginTabLink',
				href:'#',
				class:'tabLink activeTabLink'
			}).
			text("Log In")
		).
		append($('<a></a>').	// Add the register tab link
				attr({
					id:'registerTabLink',
					href:'#',
					class:'tabLink'
				}).
				text("Register")
			)
		).
		append ($('<div></div>').
			attr({
				id:'loginFormDiv',
				class:'blankDiv'
			}).
		append($('<h4></h4>').
			attr('id','loginText').
			text("Log in to your "+siteName+" Account").
			css("text-align","center")
		).
		append($('<form></form>'). // The actual login form - append all form elements to THIS, not the div
			attr('id','loginForm').
			css('text-align','center').
			append($('<input></input>').
				attr({
					id: 'userNameField',
					placeholder: 'Your User Name',
					type: 'text',
					required: 'required'
				})
			).
		append($('<input></input>').
				attr({
					id: 'passwordField',
					placeholder: 'Your Password',
					type:'password',
					required: 'required'
				})
			).
			append($('<input></input>').
				attr({
					id:'loginSubmit',
					type:'submit',
					value:'Log In',
					style:'margin-right:15px'
				})
			).
			append($('<input></input>').
					attr({
						id:'loginCancel',
						type:'submit',
						value:'Cancel'
					})
				)
			)
		).
		append($('<div></div'). // Register form div - hidden until the appropriate tab is clicked
			attr({
				id:'registerFormDiv',
				class:'blankDiv',
				style:'display:none;'
			}).
			append($('<h4></h4>').
				text('Please fill in all the fields below to register for an account').
				attr({
					id:'registerText'
				})
			).
			append($('<form></form>').
				attr({
					id:'registerForm'
				}).
				css('text-align','center').
				append($('<input></input>').
					attr({
						id:'registerNameField',
						placeholder:'The username you want to go by',
						type:'text',
						required:'required'
					})
				).
				append($('<input></input>').
						attr({
							id:'registerEmailField',
							placeholder:'Your email address',
							type:'email',
							required:'required'
						})
					).
				append($('<input></input>').
						attr({
							id:'registerPasswordField',
							placeholder:'Your desired password',
							type:'password',
							required:'required'
						})
					).
				append($('<input></input>').
						attr({
							id: 'confirmPasswordField',
							placeholder: 'Confirm your password (type it again)',
							type:'password',
							required: 'required'
						})
					).
				append($('<input></input>').
						attr({
							id:'registerSubmit',
							type:'submit',
							value:'Register'
						})
					)
				)
		).
		append($('<div></div>'). // Provider choice div - all the provider choice images and such
			attr({				// will be appended hereto
				id:'provChoiceDiv',
				class:'blankDiv'
			}).
			append($('<hr />').
				attr({
					class:'halfhr'
				})
			).
			append($('<h4></h4>').
					text("Login with your account with")	
				).
			append($('<ul></ul>'). // Set up the list which will contain the provider choice elements
				attr({
					id:'provChoiceList',
					class:'pList'
				}).
				append($('<li></li>').
					append($('<span></span>').
						text(siteName).
						attr('class','pName')
					).
					append($('<a></a>').
						attr({
							id:'providerSU',
							class:'activePImage',
							href:'#'
						}).
						append($('<img></img>').
							attr({
								src:hostLoc+'image/saneBoxIcon.png'
							})
						)
					)
				).
				append($('<li></li>').
						append($('<span></span>').
							text("Google")
						).
						append($('<a></a>').
							attr({
								id:'providerGoogle',
								href:'#'
							}).
							append($('<img></img>').
								attr({
									src:hostLoc+'image/googleBoxIcon.png'
								})
							)
						)
					).
				append($('<li></li>').
						append($('<span></span>').
							text("Yahoo!")
						).
						append($('<a></a>').
							attr({
								id:'providerYahoo',
								href:'#'
							}).
							append($('<img></img>').
								attr({
									src:hostLoc+'image/yahooBoxIcon.png'
								})
							)
						)
					).
				append($('<li></li>').
						append($('<span></span>').
							text("Facebook")
						).
						append($('<a></a>').
							attr({
								id:'providerFacebook',
								href:'#'
							}).
							append($('<img></img>').
								attr({
									src:hostLoc+'image/facebookBoxIcon.png'
								})
							)
						)
					)
				)
			)
		
		);
	
	
	// Setup the popups for the form elements
	var formPopups = {
		"#userNameField":"<h5>Your User Name</h5><p>Your user name with "+siteName+".</p>",
		"#passwordField":"<h5>Your "+siteName+" Password</h5><p>We will never ask for a password from any other account!"+
		" If you'd rather not sign up for an account with us, just choose from one of the providers below, like"+
		" Google or Facebook.",
		"#registerNameField":"<p>The user name you want. It must be more than 3 characters long, and less than 50.</p>",
		"#registerEmailField":"<p>Your email address.</p>",
		"#registerPasswordField":"<p>The password you want to use with this account. We require that it be at"+
		" least 12 characters long, and suggest you use a combination of upper and lower case letters and numbers.</p>",
		"#confirmPasswordField":"<p>Type your password again to confirm it - remember, its case-sensitive.</p>"
	};
	controlPopup(formPopups,true);
}

/**
 * Creates and controls the position of the popup 'bubble' dialog. These popups bubbles will be displayed whenever an
 * element has the mouse enter it, or if its an input element, any time it gains focus, and will disappear whenever the
 * mouse leaves the object or, if its an input element, whenever it loses focus.
 * This is based on the CODA popup tutorial found at http://jqueryfordesigners.com/coda-popup-bubbles/.
 * @param obArray Javascript object as a stand-in for an associative array. The name of the property is the element we
 * want to insert an invisible popup div beneath, while the value of the property is the html content for each popup.
 * Example: var yada = {"#registerForm":"<span>Click Here to Register!</span>", "#loginForm":"<span>Click Here To Login!</span>"};
 * @param userClearable Whether to bind actions that the user can perform that will make the popup clear away - if
 * set to false, will instead bind custome event 'clearPopup' which will have to be called from javascript to clear
 * the popup away. If true, will bind mouseleave and/or blur.
 */
function controlPopup(obArray, userClearable){
	for (var element in obArray){
		$(element).
		attr('class','trigger').
		wrap($('<div class="bubbleInfo" />')).
		after($('<div></div>').
				attr({
					class:'popup'
					}).
				html(obArray[element])
		);
	}
	
	 $('.bubbleInfo').each(function() {
		 	var time = 200;
			var hideDelay = 400;
			var hideTimer = null;
			var animating = false, shown = false;
			
			var trigger = $('.trigger', this);
			var popup = $('.popup', this).css('opacity', 0);
			
			function showPopup(){
				// Don't trigger the animation again if its currently showing
				if (hideTimer) clearTimeout(hideTimer);
				if (animating || shown){
					return;
				}else{
					// Otherwise indicate that we're currently in the process of animating the popup
					animating = true;
				}
				// Get the position of the element we want this popup to appear over, and position the popup
				// on level with it, as well as setting the popup to visible but transparent
				var position = trigger.position();
				popup.css({
					display:'block',
					opacity:0,
					left: ((position.left + trigger.width()/2) - popup.width()/2),
					top: position.top
				}).
				// now animate its opacity and position
				animate({
					top: '-=' + (popup.height()+20) + 'px',
					opacity:0.97
				},time,'swing',function(){
					animating = false;
					shown = true;
				});
				
			}
			
			function hidePopup(){
				if (hideTimer) clearTimeout(hideTimer); // Clear out any old timers
					hideTimer = setTimeout(function(){
					hideTimer = null; // null out the completed timer
					popup.
					animate({
						top: '-=' + popup.height()*2 + 'px',
						opacity:0
					},time,'swing',function(){
						shown = false;
						popup.css('display','none');
						restoreOldHtml(popup); // Restore this popup's html if it was changed
						});
					}, hideDelay);
				}
			
			/**
			 * Call this function to prevent the hidePopup process from taking place
			 */
			function cancelHide(){
				if (hideTimer) clearTimeout(hideTimer);
			}
			
			if (trigger.is(":input")){
				if (userClearable){
					$([trigger.get(0)]).bind({
						focus: showPopup,
						blur: hidePopup,
						displayPopup: showPopup,
						clearPopup: hidePopup
					});
				}else{
					$([trigger.get(0)]).bind({
						focus:showPopup,
						displayPopup: showPopup,
						clearPopup: hidePopup
					});
				}
			}else{
				if (userClearable){
					$([trigger.get(0)]).bind({
						mouseenter: showPopup,
						mouseleave: hidePopup,
						displayPopup: showPopup,
						clearPopup: hidePopup
					});
				}else{
					$([trigger.get(0)]).bind({
						mouseenter: showPopup,
						displayPopup: showPopup,
						clearPopup: hidePopup
					});
				}
			}
		if (userClearable){	
			$([popup.get(0)]).bind({
				mouseenter: showPopup,
				mouseleave: hidePopup,
				displayPopup: showPopup,
				clearPopup: hidePopup,
				click: cancelHide
			});
		}else{
			$([popup.get(0)]).bind({
				mouseenter: showPopup,
				displayPopup: showPopup,
				clearPopup: hidePopup
			});
		}
	 });
}

/**
 * Save the html of an element in data attached to it, and insert new html into its midst. If overwrite is set to
 * false, then if the element already has the oldHtml data set on it (which gets cleared whenever restoreOldHtml is
 * called), then we return without doing anything - this is to prevent overwriting stored html with new contents
 * that aren't the desired saved html (for instance, with the error popups).
 * @param element The element whose html we want to save.
 * @param newHtml The html to overwrite the current html with.
 * @param overwrite Whether to overwrite any saved html with the current stuff, or toss it.
 */
function saveOldHtml(element, newHtml, overwrite){
	element = $(element);
	oldHtml = element.data('oldHtml');
	if (oldHtml != undefined && overwrite == true){
		element.data('oldHtml',element.html());
	}else if (oldHtml == undefined){
		element.data('oldHtml',element.html());
	}
	element.html(newHtml);
}

/**
 * Restore the html of the given element from the data 'oldHtml' value stored on the element,
 * then remove that data from it so we can prepare to store future data if so desired.
 * @param element The element to restore the html of.
 */
function restoreOldHtml(element){
	oldHtml = element.data('oldHtml');
	if (oldHtml != undefined){
		element.html(oldHtml);
		element.removeData('oldHtml'); // get rid of the data now that we don't need it anymore
	}
}

/**
 * Paginates a given element, attaching a variety of behaviours to certain controls that are added to the
 * page. Expectes a jQuery object to refer to the element we want the children elements of paginated, how many
 * items we want displayed per page, and an optional itemType simple tag string, which we wil interpret as being
 * the type of element we want to paginate, ignoring others.
 * @param element jQuery object referencing the element containing our page contents.
 * @param itemsPerPage How many items we want to display per page.
 * @param pageThis A string (ie. 'div') referencing the children of element that we want to paginate. Optional.
 * If not defined, we'll simply paginate all children of element.
 * @return
 */
function paginate(element, itemsPerPage, pageThis){
	var numPages;
	// Get the number of pages we'll be making
	if (typeof(pageThis) != 'undefined'){
		numPages = Math.ceil(element.children(pageThis).length/itemsPerPage);
	}else{
		numPages = Math.ceil(element.children().length/itemsPerPage);
	}
	if (numPages > 1){ // no point in adding page selection if we don't have any pages
		
		// Hide the elements that will end up making up our pages, save for those of the first page
		if (typeof(pageThis) != 'undefined'){
			element.children(pageThis).hide().slice(0,itemsPerPage).show();
		}else{
			element.children().hide().slice(0,itemsPerPage).show();
		}
		
		if (!($('#pageControls').length)){ // if we can't find a pageControls div specified, we'll slap one in
			element.after($('<div id="pageControls"></div>'));
		}
		// Now we'll add the arrows and page numbers (within anchor links) to the div. 
		$('#pageControls').
				append($('<a href="#" title="First Page" id="pageFirst"></a>').
				html("&#171;")).
				append($('<a href="#" title="Previous Page" id="pagePrev"></a>').
				text("<")).
				append($('<span id="pageFlow"></span>'));
		for (var i=1; i <= numPages; i++){
			$('#pageFlow').append($('<a href="#" title="Page '+i+'" class="gotoPage" data-pageNum="'
					+(i-1)+'"></a>').
					text(i));
		}
		$('#pageFlow').find('.gotoPage').first().attr('class','gotoPage current'); // Set 1st link to selected
		$('#pageControls').append($('<a href="#" title="Next Page" id="pageNext"></a>').
				text('>')).
				append($('<a href="#" title="Last Page" id="pageLast"></a>').
				html("&#187;"));
	
	
		// Center the pageControls
		$('#pageControls').css({
			top: $('#pageControls').position().top,
			left: element.position().left + element.width()/2 - $('#pageControls').width()/2,
			position:'absolute'
		});
		
		// Now attach the desired behaviour to each link
		$('#pageFirst').click(function(event){
			event.preventDefault();
			$('#pageFlow').find('.gotoPage').first().trigger('click');
		});
		$('#pageLast').click(function(event){
			event.preventDefault();
			$('#pageFlow').find('.gotoPage').last().trigger('click');
		});
		$('#pagePrev').click(function(event){
			event.preventDefault();
			var prev = $('#pageFlow').find('.gotoPage').filter('.current').prev();
			if (prev.attr('class') == 'gotoPage'){
				prev.trigger('click');
			}
		});
		$('#pageNext').click(function(event){
			event.preventDefault();
			var next = $('#pageFlow').find('.gotoPage').filter('.current').next();
			if (next.attr('class') == 'gotoPage'){
				next.trigger('click');
			}
		});
		$('.gotoPage').each(function(){
			$(this).click(function(){
				event.preventDefault();
				if ($(this).attr('class') == 'gotoPage'){
					$('#pageFlow').find('.gotoPage').removeAttr('class').attr('class','gotoPage');
					$(this).attr('class','gotoPage current');
					// Get the page we want to go to, and based on the value of pageThis, slice away what we
					// don't want to see, and show what we do
					var pageNum = $(this).attr('data-pageNum'),
					pageStart = itemsPerPage * pageNum,
					pageEnd = pageStart + itemsPerPage;
					if (typeof(pageThis) != 'undefined'){
						element.children(pageThis).hide().slice(pageStart,pageEnd).fadeIn();
					}else{
						element.children().fadeOut().slice(pageStart,pageEnd).fadeIn();
					}
					window.scroll(0,0); // Scroll back to the top of the page
					// Reposition the page controls appropriately
					$('#pageControls').css({
						top: element.position().top+element.height(),
						left: element.position().left + element.width()/2 - $('#pageControls').width()/2,
						position:'absolute'
					});
				}
			});
		});
	}
}

/**
 * Set a new cookie
 * @param name The name of the cookie
 * @param value The value it should store
 * @param seconds The number of seconds we want the cookie to last for
 */
function setCookie(name,value,seconds) {
    if (seconds) {
        var date = new Date();
        date.setTime(date.getTime()+seconds);
        var expires = "; expires="+date.toGMTString();
    }
    else var expires = "";
    document.cookie = name+"="+value+expires+"; path=/";
}
/**
 * Get the value of a given cookie
 * @param name The name of the cookie whose value we want to retrieve
 * @return The value of the cookie with the given name - if none exists, null.
 */
function getCookie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i=0;i < ca.length;i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    }
    return null;
}

/**
 * Delete the given cookie
 * @param name The name of the cookie we want to delete
 * @return
 */
function deleteCookie(name) {
    setCookie(name,"",-1);
}

/**
 * Returns a random number from within a given range
 * @param lowerLimit
 * @param upperLimit
 * @return
 */
function randomRange(lowerLimit, upperLimit){
	return Math.floor(Math.random() * (upperLimit - lowerLimit + 1)) + lowerLimit;
}

/**
 * Counts the number of properties possessed by an object, relying on the fact that any element we've added
 * should have its own property. 
 * @param object The object whose properties we need a count of
 * @return
 */
function propCount(object) {
    var count = 0;
    for(var prop in object) {
        if(object.hasOwnProperty(prop)){
                ++count;
        }
    }
    return count;
}

/**
 * Takes a javascript array and iterates over it with the expectation that each value is an url to an image.
 * Loads said images and, when done doing so, calls the function defined by callback.
 * @param sources The image source object containing the urls of the images we want to load.
 * @param images The array to load the images into.
 * @param callback The function to call when we're done loading the images.
 */
function loadImages(sources, images, callback){
	var totalImages = sources.length;
	var loaded = 0;
	for (var i=0; i < totalImages; i++){
		images[i] = new Image();
		images[i].src = sources[i];
		images[i].onload = function(){
			loaded++;
			if (loaded == totalImages){
				callback();
			}
		};
	}
}

/**
 * LZW-compress a string
 * @param s The string to compress
 * @return string
 */
function lzw_encode(s) {
    var dict = {};
    var data = (s + "").split("");
    var out = [];
    var currChar;
    var phrase = data[0];
    var code = 256;
    for (var i=1; i<data.length; i++) {
        currChar=data[i];
        if (dict[phrase + currChar] != null) {
            phrase += currChar;
        }
        else {
            out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
            dict[phrase + currChar] = code;
            code++;
            phrase=currChar;
        }
    }
    out.push(phrase.length > 1 ? dict[phrase] : phrase.charCodeAt(0));
    for (var i=0; i<out.length; i++) {
        out[i] = String.fromCharCode(out[i]);
    }
    return out.join("");
}

/** 
 * Decompress an LZW-encoded string
 * @param s The string to decompress
 * @return string
 */
function lzw_decode(s) {
    var dict = {};
    var data = (s + "").split("");
    var currChar = data[0];
    var oldPhrase = currChar;
    var out = [currChar];
    var code = 256;
    var phrase;
    for (var i=1; i<data.length; i++) {
        var currCode = data[i].charCodeAt(0);
        if (currCode < 256) {
            phrase = data[i];
        }
        else {
           phrase = dict[currCode] ? dict[currCode] : (oldPhrase + currChar);
        }
        out.push(phrase);
        currChar = phrase.charAt(0);
        dict[code] = oldPhrase + currChar;
        code++;
        oldPhrase = phrase;
    }
    return out.join("");
}

/**
 * Shim layer with setTimeout fallback from http://paulirish.com/2011/requestanimationframe-for-smart-animating/
 * Usage:requestAnimFrame(animloop, element);
 * Where animLoop = the animation function which contains the request for animation frames;
 * element = the bounding element which we want to animate/update.
 */
window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       || 
          window.webkitRequestAnimationFrame || 
          window.mozRequestAnimationFrame    || 
          window.oRequestAnimationFrame      || 
          window.msRequestAnimationFrame     || 
          function(/* function */ callback, /* DOMElement */ element){
            window.setTimeout(callback, 1000 / 60);
          };
})();

/**
 * Request Interval shim which allows for the flexibility of update interval inherent in setInterval, along
 * with the performance improvements in requestAnimationFrame.
 * Relies on window.requestAnimFrame (above).
 * Usage: requestInterval(fn, delay). fn = function to execute when delay expires; delay = number of milliseconds
 * to delay between function calls.
 */
window.requestInterval = function(fn, delay) {
    if( !window.requestAnimationFrame       && 
        !window.webkitRequestAnimationFrame && 
        !window.mozRequestAnimationFrame    && 
        !window.oRequestAnimationFrame      && 
        !window.msRequestAnimationFrame)
            return window.setInterval(fn, delay);

    var start = new Date().getTime(),
    handle = new Object();

    function loop() {
        var current = new Date().getTime(),
        delta = current - start;

        if(delta >= delay) {
            fn.call();
            start = new Date().getTime();
        }

        handle.value = requestAnimFrame(loop);
    };

    handle.value = requestAnimFrame(loop);
    return handle;
};

window.clearRequestInterval = function(handle) {
    window.cancelAnimationFrame ? window.cancelAnimationFrame(handle.value) :
    window.webkitCancelRequestAnimationFrame ? window.webkitCancelRequestAnimationFrame(handle.value)   :
    window.mozCancelRequestAnimationFrame ? window.mozCancelRequestAnimationFrame(handle.value) :
    window.oCancelRequestAnimationFrame ? window.oCancelRequestAnimationFrame(handle.value) :
    window.msCancelRequestAnimationFrame ? msCancelRequestAnimationFrame(handle.value) :
    clearInterval(handle);
};
