////////////////////////////////////////////////////////////////
// HomeNet JS Core Site Library
// User Class
// (c) 2010, HomeNet Automotive LLC
// ************************
// User account for use with any other module to save and interact with a user login. Presents simple decoupled interface 
// for user data so it can serve for any purpose. In many cases for using, calling modules will do bulk of work and mainly just 
// passing compiled data here and using responses. Note that just because a user validates as logged in here doesnt mean we can 
// assume they are actually logged in so do proper server-side checks on any called services.
// ************************
// Subscribable Events (bind anything to these custom global events for nice, decoupled integration)
//   'hnsiteUserLogin': fires off if the user is logged in when the document loads and then whenever a users status changes from being logged out to logged in
//   'hnsiteUserLogout': fires off if the user is logged out when the document loads and then whenever a users status changes from being logged in to logged out
// ************************
// Possible Refactoring notes
//   - setters for account need error checking, assumes we pass in account settings node of JSON responses which is consistent so okay for now
//   - events for login and logout should be called from setter and clear methods of Account only if they are always invoked when this occurs, also first time
//     running may ideally need to be handled another way and tracked/handled from Account internally
////////////////////////////////////////////////////////////////

if (typeof HNSITE == 'undefined') HNSITE = {};
if (typeof HNSITE.Modules == 'undefined') HNSITE.Modules = {};

// ***************************************
// Define our Modules.User namespace
// ***************************************
HNSITE.Modules.User = {};

// <summary>
// Cached account settings for the current user with accessors for control. Maintains our
// session state for the current user.
// </summary>
HNSITE.Modules.User.Account = (function() {

	// Privately stored account settings
	var _settings = {
		SiteID: null, 
		LoginID: null, 
		Name: null,
		Email: null
	};
	
	// Retrieves account settings
	var _getter = function() {
		return _settings;
	};
	
	// Sets and updates account settings
	var _setter = function(accountSettings) {
		_settings.SiteID = accountSettings.SiteID;
		_settings.LoginID = accountSettings.LoginID;
		_settings.Name = accountSettings.Name;
		_settings.Email = accountSettings.Email;
	};
	
	// Checks based on whether mandatory data is present if user is logged in
	var _isLoggedIn = function() {
		return (_settings.SiteID != null && _settings.LoginID != null && _settings.Name != null && _settings.Email != null);
	};
	
	// Clears login credentials
	var _clearSession = function() {
		_settings.SiteID = null;
		_settings.LoginID = null;
		_settings.Name = null;
		_settings.Email = null;
	};
	
	// Expose our public methods
	return {
		GetSettings: _getter, 
		SetSettings: _setter,
		IsLoggedIn: _isLoggedIn, 
		ClearSession: _clearSession
	};
	
})();

// <summary>
// The User class initializer which is executed (from this file) once the page has loaded. For now, we are just checking
// their login status and making sure we check at a set interval so our event subscriptions are timely.
// </summary>
HNSITE.Modules.User.Init = function() {
	// Runs our very first login check once the page has loaded to get their initial state. We disable the login events that normally
	// fire off when a status changes so that on this first run we can fire them off to reflect the users initial state instead.
	HNSITE.Modules.User.CheckLogin({
		disableLoginEvents: true,
		onSuccess: function() {
			$j.event.trigger('hnsiteUserLogin');
		}, 
		onFail: function() {
			$j.event.trigger('hnsiteUserLogout');
		}
	});
	// Keep checking their status in the background every two minutes so state and events are timely
	window.setInterval(function() { HNSITE.Modules.User.CheckLogin(); }, 60000);
};

// <summary>
// Performs a User web service - every requesting User method is called through this interface
// Arguments: 
//    serviceName (string): Name of the service we wish to call (must be on the accepted method list)
//    options (object): Flexible options object for passing callbacks and data to send with the request
//       -> onAjaxSuccess (function): Called on successful ajax response, needs an argument for the response. For each method calling a service, this callback  
//             will be created by that method and not directly input as any argument. Typically the calling method will accept optional success/fail callbacks
//             which are called from this constructed callback (onAjaxSuccess) so that the semantics of those callback actions are relevant to the method name called.
//       -> onAjaxError (function): Called when ajax response errors. Normally the calling method will let the user input this callback as an argument and carry 
//             it over to here but for special circumstances it may be indirectly wrapped in a constructed callback just like onAjaxSuccess.
//       -> postData (object): Simple key/pair object of post data to send with this request.
// </summary>
HNSITE.Modules.User.CallService = (function() {
	// Private webservice info required
	var _webservice = {
		Url: '/framework/module-user/user.webservices.asp', 
		ServiceList: ['login', 'register', 'logout', 'confirmlogin', 'reset-password', 'change-password']
	};
	
	// Return the constructor function for calling a service
	return function(serviceName, options) {
		if ($j.inArray(serviceName, _webservice.ServiceList) < 0) return;
		
		var requiredData = {
			method: serviceName, 
			r: HNSITE.Utils.Stamp()
		};
		
		if (typeof options.onAjaxSuccess != 'function')
			options.onAjaxSuccess = function() {};
		if (typeof options.onAjaxError != 'function')
			options.onAjaxError = function() {};
		if (HNSITE.Utils.IsNullOrEmpty(options.postData))
			options.postData = requiredData;
		else
			options.postData = $j.extend({}, requiredData, options.postData)

		$j.ajax({
			type: 'POST',
			cache: false,
			url: _webservice.Url, 
			data: options.postData,
			error: options.onAjaxError,
			success: options.onAjaxSuccess,
			dataType: 'json'
		});
	};
})();

// <summary>
// Checks if the user is currently logged in with a valid session
// Options: 
//    onSuccess (function): Callback for if the current user is found to be successfully logged in
//    onFail (function): Callback for if the current user is not logged in
//    onError (function): Callback which is run if the ajax request has an error when trying to retrieve the response
//    disableLoginEvents (bool): If true, we will not fire off our global login events which is useful for now only when
//       the page first loads and we want to change the logic behind how we fire the events. So basically, never use this.
// </summary>
HNSITE.Modules.User.CheckLogin = function(options) {
	var self = this;
	
	// Validate options and callbacks
	if (typeof options != 'object')
		options = {};
	if (typeof options.onSuccess != 'function')
		options.onSuccess = function() {};
	if (typeof options.onFail != 'function')
		options.onFail = function() {};
	if (typeof options.onError != 'function')
		options.onError = function() {};
	if (options.disableLoginEvents !== true)
		options.disableLoginEvents = false;
	
	// Make the service call and perform callbacks within appropriate contexts of our method name
	self.CallService('confirmlogin', {
		onAjaxSuccess: function(response) {
			if (response.is_logged_in == 'true') {
				if (HNSITE.Modules.User.Account.IsLoggedIn() === false && (!options.disableLoginEvents))
					$j.event.trigger('hnsiteUserLogin');
				self.Account.SetSettings(response.account_settings);
				options.onSuccess();
			}
			else {
				if (HNSITE.Modules.User.Account.IsLoggedIn() === true && (!options.disableLoginEvents))
					$j.event.trigger('hnsiteUserLogout');
				self.Account.ClearSession();
				options.onFail();
			}
		}, 
		onAjaxError: options.onError
	});
};

// <summary>
// Login a user to their account
// Options: 
//    onSuccess (function): Callback for when the user successfully logs in to their account
//    onFail (function): Callback for when the users login fails due to an input error that made it this far or more likely 
//       just bad credentials after trying to verify account on the server
//    onError (function): Callback which is run if the ajax request has an error when trying to retrieve the response
// </summary>
HNSITE.Modules.User.Login = function(credentials, options) {
	var self = this;

	// Validate options and callbacks
	if (typeof options != 'object')
		options = {};
	if (typeof options.onSuccess != 'function')
		options.onSuccess = function() {};
	if (typeof options.onFail != 'function')
		options.onFail = function() {};
	if (typeof options.onError != 'function')
		options.onError = function() {};
	
	// Validate user data
	if (HNSITE.Utils.IsNullOrEmpty(credentials.email) || (!HNSITE.UI.Validation.Regex.QuickTests.IsEmail(credentials.email))) {
		options.onFail();
		return;
	}
	else if (HNSITE.Utils.IsNullOrEmpty(credentials.password)) {
		options.onFail();
		return;
	}
	else 
		credentials = HNSITE.Data.CleanParams(credentials, ['email', 'password']);
	
	// Make the service call and perform callbacks within appropriate contexts of our method name
	self.CallService('login', {
		onAjaxSuccess: function(response) {
			if (response.login_status == 'success') {
				if (HNSITE.Modules.User.Account.IsLoggedIn() === false)
					$j.event.trigger('hnsiteUserLogin');
				self.Account.SetSettings(response.account_settings);
				options.onSuccess();
			}
			else {
				if (HNSITE.Modules.User.Account.IsLoggedIn() === true)
					$j.event.trigger('hnsiteUserLogout');
				self.Account.ClearSession();
				options.onFail();
			}
		}, 
		onAjaxError: options.onError,
		postData: credentials
	});
};

// <summary>
// Logout a user from their account
// Options: 
//    onSuccess (function): Callback which is run after the user is logged out of their account
//    onError (function): Callback which is run if the ajax request has an error when trying to retrieve the response
// </summary>
HNSITE.Modules.User.Logout = function(options) {
	var self = this;

	// Validate options and callbacks
	if (typeof options != 'object')
		options = {};
	if (typeof options.onSuccess != 'function')
		options.onSuccess = function() {};
	if (typeof options.onError != 'function')
		options.onError = function() {};
	
	// Make the service call and perform callbacks within appropriate contexts of our method name
	self.CallService('logout', {
		onAjaxSuccess: function(response) {
			if (response.logout_status == 'success') { // always returns successful
				if (HNSITE.Modules.User.Account.IsLoggedIn() === true)
					$j.event.trigger('hnsiteUserLogout');
				self.Account.ClearSession();
				options.onSuccess();
			}
		}, 
		onAjaxError: options.onError
	});
};

// <summary>
// Registers a new user account and logs them in for first time. Returns a status code and we allow a callback for
// each status that could result.
// Arguments: 
//    userData (object): Simple object containing our post data for the new user account. Accepts values for 'fname', 'lname', 'phone', 'email', and 
//       'password'. Only 'email' and 'password' are required though since they are the bare minimum requirements for an account.
//    options (object): Flexible options object for passing callbacks and data to send with the request
//       -> onSuccess (function): Called if the user is registered successfully and subsequently logged in for first time
//       -> onInvalidInput (function): Called if the mandatory user data does not validate (hopefully prevented by client-side checks here), accepts one 
//             argument which is for an object we pass in with the field name that triggered the error and its value.
//       -> onAlreadyExists (function): Called if the user account for the email provided already exists for this site
//       -> onUnknownError (function): Should never occur; this is called when a new account is successfully created but when trying to log in 
//             the user to their new account, it failed to do so. May just need to re-initiate login since the account has been created.
// </summary>
HNSITE.Modules.User.RegisterAccount = function(userData, options) {
	var self = this;
	var buildInputError = function(fieldName, attemptedValue) {
		return {field: fieldName, value: attemptedValue};
	};
	
	// Validate options
	if (typeof options != 'object')
		options = {};
	if (typeof options.onSuccess != 'function')
		options.onSuccess = function() {};
	if (typeof options.onInvalidInput != 'function')
		options.onInvalidInput = function() {};
	if (typeof options.onAlreadyExists != 'function')
		options.onAlreadyExists = function() {};
	if (typeof options.onUnknownError != 'function')
		options.onUnknownError = function() {};
	
	// Validate user data
	if (HNSITE.Utils.IsNullOrEmpty(userData.email) || (!HNSITE.UI.Validation.Regex.QuickTests.IsEmail(userData.email))) {
		options.onInvalidInput( buildInputError('email', userData.email) );
		return;
	}
	else if (HNSITE.Utils.IsNullOrEmpty(userData.password)) {
		options.onInvalidInput( buildInputError('password', userData.password) );
		return;
	}
	else 
		userData = HNSITE.Data.CleanParams(userData, ['fname', 'lname', 'phone', 'email', 'password']);
	
	// Make the service call and perform callbacks within appropriate contexts of our method name
	self.CallService('register', {
		onAjaxSuccess: function(response) {
			if (response.registration_status == 'success') {
				$j.event.trigger('hnsiteUserLogin');
				self.Account.SetSettings(response.account_settings);
				options.onSuccess();
			}
			else if (response.registration_status == 'account-exists')
				options.onAlreadyExists();
			else if (response.registration_status == 'invalid-email')
				options.onInvalidInput( buildInputError('email', userData.email) );
			else if (response.registration_status == 'invalid-password')
				options.onInvalidInput( buildInputError('password', userData.password) );
			else if (response.registration_status == 'unknown-error')
				options.onUnknownError();
		}, 
		onAjaxError: options.onError, 
		postData: userData
	});
};

// <summary>
// In the case of a lost account password, resets and emails the user a new password which is active immediately.
// Arguments: 
//    emailAddress (string): Email address (username) for whom we need to mail a new password
//    options (object): Flexible options object for passing callbacks and data to send with the request
//       -> onSuccess (function): Called if the user was successfully emailed a reset password
//       -> onInvalidInput (function): Called if the mandatory user data does not validate (hopefully prevented by client-side checks here), accepts one 
//             argument which is for an object we pass in with the field name that triggered the error and its value.
//       -> onAccountDoesntExist (function): Called if the email address provided doesnt belong to any login record
// </summary>
HNSITE.Modules.User.ResetPassword = function(emailAddress, options) {
	var self = this;
	var buildInputError = function(fieldName, attemptedValue) {
		return {field: fieldName, value: attemptedValue};
	};
	
	// Validate options
	if (typeof options != 'object')
		options = {};
	if (typeof options.onSuccess != 'function')
		options.onSuccess = function() {};
	if (typeof options.onInvalidInput != 'function')
		options.onInvalidInput = function() {};
	if (typeof options.onAccountDoesntExist != 'function')
		options.onAccountDoesntExist = function() {};
	
	// Validate user data
	if (HNSITE.Utils.IsNullOrEmpty(emailAddress) || (!HNSITE.UI.Validation.Regex.QuickTests.IsEmail(emailAddress))) {
		options.onInvalidInput( buildInputError('email', emailAddress) );
		return;
	}
	
	// Make the service call and perform callbacks within appropriate contexts of our method name
	self.CallService('reset-password', {
		onAjaxSuccess: function(response) {
			if (response.reset_status == 'success')
				options.onSuccess();
			else if (response.reset_status == 'no-account')
				options.onAccountDoesntExist();
			else if (response.reset_status == 'invalid-email')
				options.onInvalidInput( buildInputError('email', emailAddress) );
		}, 
		onAjaxError: options.onError, 
		postData: {email: emailAddress}
	});
};

// <summary>
// Changes the password for a logged in user
// Arguments: 
//    passwordSet (object): The requested new password and the confirmation new password, should have keys 'password' and 'confirmation' for values
//    options (object): Flexible options object for passing callbacks and data to send with the request
//       -> onSuccess (function): Callback for when the user's password is successfully changed
//       -> onSessionMissing (function): Callback for when the users password change fails because they were not logged-in at the time
//       -> onPasswordsDontMatch (function): Callback for if the confirmation password does not match the requested new password, bad input
// </summary>
HNSITE.Modules.User.ChangePassword = function(passwordSet, options) {
	var self = this;

	// Validate options and callbacks
	if (typeof options != 'object')
		options = {};
	if (typeof options.onSuccess != 'function')
		options.onSuccess = function() {};
	if (typeof options.onSessionMissing != 'function')
		options.onSessionMissing = function() {};
	if (typeof options.onPasswordsDontMatch != 'function')
		options.onPasswordsDontMatch = function() {};
	
	// Validate user data
	if (passwordSet.password != passwordSet.confirmation) {
		options.onPasswordsDontMatch();
		return;
	}
	
	// Make the service call and perform callbacks within appropriate contexts of our method name
	self.CallService('change-password', {
		onAjaxSuccess: function(response) {
			if (response.password_status == 'success') 
				options.onSuccess();
			else 
				options.onSessionMissing();
		}, 
		onAjaxError: options.onError,
		postData: {newPassword: passwordSet.password}
	});
};

// <summary>
// Creates on the fly the html structure for a login and presents the dialog centered on the screen. Not needed but nice
// option to have available if we need to place a login in various places.
// </summary>
HNSITE.Modules.User.PresentLoginDialog = function() {
	// build html for form contents that dialog and append to body
	// wireup submit button in dialog form to Login service
	// on error just give an alert dialog, on success do a location.reload(true)
};

// <summary>
// Storage for any methods we create over time for Carvenience tabs related to implementing this User class. Each method in the
// object is independant of each other and each has its own specific, custom purpose and should match up to a specific Carvenience
// include file for the tab. These methods are specified in the Carvenience configuration where we provide a bindings function per 
// tab. When Carvenience calls the bindings for the tab, it changes the context of 'this' to refer to the parent node for the tab to 
// be able to bind everything up quickly. Carvenience tabs are created and destroyed each time via ajax so each time it's viewed it
// pulls fresh DOM content and rebinds with the method so keep in mind when designing.
// </summary>
HNSITE.Modules.User.Carvenience = {

	// Login and Registration panel for basic "Your Account" type of page
	//  -> implementation for: module-user/carvenience-account-basic.asp
	BasicAccountTab: function() {
		var tabSelf = this;
				
		// Bind the login form
		$j(tabSelf).find(".login-button").click(function(event) {
			event.preventDefault();
			var loginForm = $j(this).closest("form");
			loginForm.find(".server-messages").text('');
			if (HNSITE.UI.Validation.SimpleValidate({context: loginForm})) {
				var email = loginForm.find("input[name='email']").val(), 
					password = loginForm.find("input[name='password']").val();
				HNSITE.Modules.User.Login({ email: email, password: password }, {
					onSuccess: function() {
						HNSITE.Modules.Carvenience.LoadTab(); // reloads current tab
					}, 
					onFail: function() {
						loginForm.find(".server-messages").text('Failed logging into user account with the specified email and password. Please try again.');
					}
				});
			}
		});
		
		// Bind the forgot password form
		$j(tabSelf).find(".forgot-password-button").click(function(event) {
			event.preventDefault();
			var forgotForm = $j(this).closest("form");
			forgotForm.find(".server-messages").text('');
			if (HNSITE.UI.Validation.SimpleValidate({context: forgotForm})) {
				var email = forgotForm.find("input[name='email']").val();
				HNSITE.Modules.User.ResetPassword(email, {
					onSuccess: function() {
						forgotForm.find(".server-messages").text('A new password has just been emailed to you at {0}'.format(email));
						forgotForm.find("input[name='email']").attr("disabled", true);
					}, 
					onAccountDoesntExist: function() {
						forgotForm.find(".server-messages").text('Email address specified is not registered to any account');
					},
					onInvalidInput: function(error) {
						forgotForm.find(".server-messages").text("Error trying to send new password, please check your {0}".format(error.field));
					}
				});
			}
		});
		
		// Bind the change password form
		$j(tabSelf).find(".change-password-button").click(function(event) {
			event.preventDefault();
			var changeForm = $j(this).closest("form");
			changeForm.find(".server-messages").text('');
			if (HNSITE.UI.Validation.SimpleValidate({context: changeForm})) {
				var passwordSet = {
					password: changeForm.find("input[name='password']").val(),
					confirmation: changeForm.find("input[name='confirmation']").val()
				};	
				HNSITE.Modules.User.ChangePassword(passwordSet, {
					onSuccess: function() {
						changeForm.find(".server-messages").text('You password change was successful');
					}, 
					onSessionMissing: function() {
						changeForm.find(".server-messages").text('You must be logged in to change your password');
					},
					onPasswordsDontMatch: function() {
						changeForm.find(".server-messages").text('Passwords do not match, please try again');
					}
				});
			}
		});
		
		// Bind the register new user form
		$j(tabSelf).find(".register-button").click(function(event) {
			event.preventDefault();
			var registerForm = $j(this).closest("form");
			registerForm.find(".server-messages").text('');
			if (HNSITE.UI.Validation.SimpleValidate({context: registerForm})) {
				var userData = {};
				registerForm.find("input[type='text'],input[type='password']").each(function() {
					userData[this.name] = this.value;
				});
				HNSITE.Modules.User.RegisterAccount(userData, {
					onSuccess: function() { HNSITE.Modules.Carvenience.LoadTab(); },
					onAlreadyExists: function() {
						registerForm.find(".server-messages").text("Please use another email address, this one is already registered.");
					}, 
					onInvalidInput: function(error) {
						registerForm.find(".server-messages").text("Error trying to register account, please check your {0}".format(error.field));
					}
				});
			}
		});
		
		// Bind the logout button for when someones signed in
		$j(tabSelf).find(".logout-button").click(function(event) {
			HNSITE.Modules.User.Logout({
				onSuccess: function() {
					HNSITE.Modules.Carvenience.LoadTab(); // reloads current tab
				}
			});
		});
	}
		
};

// Run our initializer here since this specific class is meant to be automagical from the shadows
$j(function() {
	HNSITE.Modules.User.Init();
});

