﻿/**
 * Contains generic validation functions for form controls
 * @package FormValidation.js
 * @dependancy FamilyWare.js
 */

var formValidation = {};
formValidation._count = 0;
formValidation._fields = [];
formValidation._invalidDateMsg = "Onmogelijke datum waarde ingevoerd";
formValidation._unlikelyDateMsg = "De ingevoerde datum is zeer onwaarschijnlijk";

/**
 * Form validation initialization routine. Registers proper events, create needed objects and so on.
 * @usage formValidation.init();
 */
formValidation.init = function() {
		
} // init()

/**
 * Generates an ID which is used for elements which have no ID value set.
 * @return string An ID value.
 **/
formValidation.genId = function() {
	formValidation._count++;
	var tmpDate = new Date();
	return "id" + formValidation._count + tmpDate.getHours() + tmpDate.getMinutes() + tmpDate.getSeconds() + tmpDate.getMilliseconds();
}

/**
 * Manages the visual consequences of raising errors and warnings on form elements.
 * @param DOMElement|String Required. The element to apply visual warning/error on.
 * @param integer Required. Error level. 0 = Normal, 1 = OK, 2 = Warning, 3 = Error.
 * @param String Not required. The error that occurred.
 * @param String Not required. Name of the function (or other entity) which raised the error.
 * @usage formValidation.setStatus();
 */
formValidation.applyStatus = function(el, errStatus, errText, callerID) {
	
	var bgColor = "#FFFFFF";
	var element = (typeof(el) === "string" ? $(el) : el);
	var oldTitle = "";
	
	if(typeof(element) !== "undefined") {
	
		var status = formValidation.getField(element.id);

		switch(errStatus) {
			case 0: // Normal (not mandatory, no value)
				bgColor = "";
				break;
			case 1: // OK
				bgColor = "#CCFFAA";
				break;
			case 2: // Warning
				bgColor = "#FFFF19";
				break;
			case 3: // Error
				bgColor = "#F5C7D4";
				break;
		}
		
		// Apply styles etc.
		element.style.backgroundColor = bgColor;
		
		// Change title
		if(errStatus >= 0) {
			if(typeof(errText) === "string") {
				oldTitle = element.getAttribute("title");
				element.setAttribute("title", errText); 
			}
		}
		else {
			// Try to restore original title
			if(status !== null) {
				element.setAttribute("title", status[1].oldTitle);
			}
		}
		
		// Register status:
		if(status !== null) {
			status[1].status = errStatus;
			status[1].statusBy = callerID;
			status[1].oldTitle = oldTitle;
		}
		else {
			formValidation._fields.push([element.id, { "status": errStatus, "statusBy": callerID, "oldTitle": oldTitle }]);
		}
		
	}
	
} // applyStatus();

/**
 * Searches for a given field element meta information.
 * @return Array|null Contains 2 elements: the first element is the DOMElement ID, the second is a JSON object containing additional information.
 * @param string The ID of the DOMElement to search for.
 * @param string Not required. The string of an entity which has set a status for the DOMElement.
 */
formValidation.getField = function(elementID, statusByID) {
	
	var fld = formValidation._fields;
	
	for(x = 0; x < fld.length; x++) {
		if(fld[x][0] == elementID) {
			if(typeof(statusByID) !== "undefined" && typeof(statusByID) !== undefined) {
				if(fld[x][1].statusBy == statusByID) {
					return fld[x];
				}
			}
			else {
				return fld[x];
			}
		}
	}
	
	return null;
		
}

/**
 * Checks if there's an AJAX validation command to be executed.
 * @return JSON Contains the validation information.
 * @param DOMElement|String The DOMElement (or its ID) to validate.
 */
formValidation.validateCmd = function(el, handler) {
	
	var element = (typeof(el) === "string" ? $(el) : el);	
	var cmd = element.getAttribute("validatecmd");
	var cmdAt = element.getAttribute("validatecmdatserver");
	var fnHandler = null;
	
	if(typeof(cmd) === "string") {
		
		if(typeof(cmdAt) === "string" && cmdAt == "false") {
			return eval(cmd)(element);
		}
		else {
			var ajax = GetXmlHttpRequestObject();
			DoAsyncXmlRequest(ajax, "FormValidation.aspx?Action=" + cmd, validateCmd_onReadyState);		
		}	
	}
	
	/**
	 * onReadyState event handler for the ajax call
	 */
	function validateCmd_onReadyState() {
		if(ajax !== null) {
			if(ajax.readyState === 4) {
				if(ajax.status === 200) {
					validateCmd_onCallback(ajax);
				}
				else {
					validateCmd_onError(ajax);
				}
			}
		}
	}
	
	/**
	 * Function to handle succesful ajax requests
	 */
	function validateCmd_onCallback(xmlhttp) {
		var json = toJSON(xmlhttp.responseText);
		if(typeof(handler) === "function") {
			handler(true, xmlhttp, json);
		}
	}
	
	/**
	 * Function to handle unsuccesful ajax requests
	 */
	function validateCmd_onError(xmlhttp) {
		if(typeof(handler) === "function") {
			handler(false, xmlhttp, null);
		}
	}
} 

/**
 * Formats the date of a date control and overwrites the value with the formatted value. 
 *   This is to keep data entry as simple as possible.
 * @return integer The error level based on the field's value. 0 = No date, 1 = OK, 2 = Warning, 3 = Error.
 * @param DOMElement|String Required. The HTML form element to auto format the date on.
 * @param String A delimiter character. Not required. Defaults to '-'.
 * @usage window.alert(formValidation.formatDate("DocEntryDate"));
 */
formValidation.formatDate = function (el, delimiterChar) {
	
	var element = null;
	var day, month, year;				

	// Get date from some other element
	element = (typeof(el) === "string" ? $(el) : el);
	
	if(typeof(element.value) !== "undefined") {

		var retVal = "";
		var delimiter = (typeof(delimiterChar) === "string" ? delimiterChar : "-");
		var date = new Date();
		var errorLevel = 0;
		var isBDP = false;
		
		// Scenario 1. Get each date "part" by splitting the value up by the first alpha-numeric character.
		// This scenario allows for padding day/month values. In the numeric only scenario this is not always determinable.
		var tmpDelimiter = null;
		var tmpVal = element.value;
		var tmpVal = tmpVal.replace(/ /g, ""); // Remove spaces
		
		if(tmpVal == "") {
			return 0; // This function does not check if there should be a date present...
		}
		
		for(y = 0; y < tmpVal.length; y++) {
			if(isNaN(tmpVal.substr(y, 1))) {
				tmpDelimiter = tmpVal.substr(y, 1);
				break;
			}
		}
		
		if(tmpDelimiter !== null) {
			var tmpDate = tmpVal.split(tmpDelimiter);
			
			if(tmpDate.length > 3 || tmpDate.length < 2) {
				// This can never be a valid date.
				formValidation.applyStatus(element, 3, formValidation._invalidDateMsg, "formatDate.checkDate");
			}
			
			var arrCount = 0;
			for(arrCount = 0; arrCount < tmpDate.length; arrCount++) {
				
				if(tmpDate[arrCount] == "") {
					formValidation.applyStatus(element, 3, formValidation._invalidDateMsg, "formatDate.checkDate");							
					return 3;
				}
				
				// Pad single digits
				if(tmpDate[arrCount].length == 1) {
					tmpDate[arrCount] = "0" + tmpDate[arrCount];					
				}
				
				if(arrCount == 0) {
					day = tmpDate[arrCount];
				}
				
				if(arrCount == 1) {
					month = tmpDate[arrCount];
				}
				
				// Pad year value
				if(tmpDate[arrCount].length == 2 && arrCount == 2) {
					if(extractNumbers(tmpDate[arrCount]) > 50) {
						tmpDate[arrCount] = "19" + tmpDate[arrCount];						
					}
					else {
						tmpDate[arrCount] = "20" + tmpDate[arrCount];					
					}
				}
				
				// Build new date string
				if(arrCount < (tmpDate.length - 1)) {
					retVal += tmpDate[arrCount] + delimiter;
				}
				else {
					year = tmpDate[arrCount];
					retVal += tmpDate[arrCount];
				}	
				
			}

			// No year was found, append current year
			if(retVal.length == 5) {
				year = new Date().getFullYear() 
				retVal += delimiter + year;
			}
			
			// Check if the date is valid, if not flag for an error.
			checkDate(year, month, day);

			// Overwrite input field value and return the formatted date.
			if(element.value === retVal) {
				callBDPHandler();									
				return 0;
			}
			else if(errorLevel == 0) {
				element.value = retVal;					
				callBDPHandler();						
				return 0;
			}
			else {
				return errorLevel;		
			}
		}

		// Scenario 2. Use the numeric only approach.
		var val = element.value;
		val = val.replace(/[- _\/]/g, "");

		month = (new Date().getMonth() + 1).toString();
		if(month.length == 1) { month = "0" + month; }
		year = date.getFullYear();
						
		if(val.length >= 4) {
			day = val.substr(0, 2);			
			month = val.substr(2, 2);
		}
		else if(val.length == 1 || val.length == 2) {
			if(extractNumbers(val) === 0) {
				element.value = val;
				formValidation.applyStatus(element, 3, formValidation._invalidDateMsg, "formatDate.checkDate");				
				return 3;
			}
			
			// Append current month and year, check date, leave.
			val = (val.length == 1 ? "0" + val : val);			
			element.value = val + "-" + month + "-" + year;
			checkDate(year, month, val);
			callBDPHandler();
			return 0;
		}
						
		// Check length of result
		switch(val.length) {
				
			case 4:
				year = date.getFullYear();				
				break;
			
			case 6:
				if(extractNumbers(val.substr(4, 2)) > 50) {
					year = "19" + val.substr(4, 2);
				}
				else {
					year = "20" + val.substr(4, 2);
				}
				break;
			
			case 8:
				year = val.substr(4, 4);
				break;
			
			default:
				// Despite trying to format the date in two ways, it failed.			
				// Always flag for an error when an invalid date is entered.
				formValidation.applyStatus(element, 3, formValidation._invalidDateMsg, "formatDate.checkDate");			
				return errorLevel;
		}
		
		retVal = day + delimiter + month + delimiter + year;
		checkDate(year, month, day);

		// Overwrite input field value and return the formatted date.		
		if(element.value == retVal) {
			callBDPHandler();									
			return 0;
		}
		else if(errorLevel == 0) {
			element.value = retVal;
			callBDPHandler();						
			return 0;
		}
		else {
			return errorLevel;		
		}
	}
	
	/**
	 * Calls the BasicDatePicker OnClientAfterSelectionChanged event handler if any.
	 */
	function callBDPHandler() {
			try {
				bdpEl = eval(element.id.replace("_TextBox", ""));
				eval(element.getAttribute("OnClientAfterSelectionChanged"))(bdpEl);
			}
			catch(e) {
			}	
	}
	
	/**
	 * Checks if the given date is valid.
	 */
	function checkDate(year, month, day) {
			var testDate = Date.parse(month + "-" + day + "-" + year);
			if(testDate === null) {
				formValidation.applyStatus(element, 3, formValidation._invalidDateMsg, "formatDate.checkDate");
			}
			else {
				if(year <= 1753)  {
					// This is done for SQL Server which can't handle dates before 1753.
					// This validation function is generic however, and can't assume it must not
					// accept otherwise valid dates. A warning is thrown instead of an error for this reason.
					formValidation.applyStatus(element, 2, formValidation._unlikelyDateMsg, "formatDate.checkDate");
				}
				else {
					var status = formValidation.getField(element.id, "formatDate.checkDate");
					if(status !== null) {
						// Check if we need to correct a previous set status.
						// If the field has a status set by this function, we can
						// safely change the status back to normal.
						formValidation.applyStatus(element, 0);
					}				
				}
			}
	}
	
	return errorLevel;
	
} // formatDate()

/**
 * Checks the strength of a given password.
 * @param String Password to validate the strength of
 * @returns Number 0 = weak, 1 = medium, 2 = strong, 3 = very strong.
 */  
formValidation.pwdStrength = function(pwd) {
	
	var result = 0;
	var score = 0;
	
	if(typeof(pwd) !== "string") {
		return 0;
	}
	
	// If the text is all the same character, it's always a weak password.
	if(formValidation.hasSameChars(pwd)) {
		return 0;
	}

	var hasUpper   = formValidation.hasUpper(pwd);
	var hasLower   = formValidation.hasLower(pwd);
	var hasNumbers = formValidation.hasNumbers(pwd);
	var hasAlpha   = formValidation.hasAlpha(pwd);	

	// Give a point for every measure type		
	if(hasUpper)   { score += 1; }	
	if(hasLower)   { score += 1; }		
	if(hasNumbers) { score += 1; }	
	if(hasAlpha)   { score += 1; }
	
	// Subtract points for lack of variation
	if(score == 1) {
		score -= 3;
	}	
	
	// Give bonus points for combinations
	if(hasUpper && hasLower && hasNumbers && hasAlpha) {
		score += 3;
	}
	else if(hasUpper && hasLower && hasNumbers) {
		score += 2;
	}
	else if(hasUpper && hasLower && hasAlpha) {
		score += 2
	}
	else if(hasUpper && hasNumbers) {
		score += 1;
	}
	else if(hasUpper && hasAlpha) {
		score += 1;
	}
	else if(hasLower && hasNumbers) {
		score += 1;
	}
	else if(hasLower && hasAlpha) {
		score += 1;
	}
	
	// Length < 8 is always considered a weak password
	if(pwd.length < 8) { 
		return 0;
	}	
	// Only passwords with length of 14+ can be a very strong password.
	// This does not mean a length of 14+ always is a very strong password.
	if(pwd.length >= 14) {
		score += 6;
	}	
	// Inbetween we give some credit, but not too much.
	if(pwd.length >= 8 && pwd.length < 14) {
		score += 3;
	}
	
	// Evaluate score:
	if(score > 10) {
		result = 3; // Very strong
	}
	
	if(score >= 7 && score <= 10) {
		result = 2; // Strong
	}
	
	if(score >= 4 && score <= 6) {
		result = 1; // Medium
	}
	
	if(score < 4) {
		result = 0; // Weak
	}
	
	return result;
	
} // pwdStrength()

/**
 * Checks for upper case letters	
 */
formValidation.hasUpper = function(str) {
	return /[A-Z]/.test(str);
}
	
/**
 * Checks for lower case letters	
 */
formValidation.hasLower = function(str) {
	return /[a-z]/.test(str);
}

/**
 * Checks for numbers	
 */	
formValidation.hasNumbers = function(str) {
	return /[0-9]/.test(str);
}

/**
 * Checks for other characters	
 */	
formValidation.hasAlpha = function(str) {
	return /[^A-Za-z0-9]/.test(str);
}

/**
 * Checks if a string consist of one type of character like 11111 or aaaaaa
 */
formValidation.hasSameChars = function(str) {
	var lastchar = "";
	for(var x = 0; x < str.length; x++) {
		currchar = str.charCodeAt(x);
		if(x > 0 && lastchar != currchar) {
			return false;
		}
		lastchar = currchar;		
	}
	return true;
}

/**
 * Evaluates the strength of a given password.
 */
var pwdstr_msg_0, pwdstr_msg_1, pwdstr_msg_2, pwdstr_msg_3 = "";
var pwdstr_0, pwdstr_1, pwdstr_2, pwdstr_3 = "";  
formValidation.evalPwdStrength = function(obj) {

	var txt = obj.value;						
	var hasNr = formValidation.hasNumbers(txt);
	var hasAn = formValidation.hasAlpha(txt);
	var hasLc = formValidation.hasLower(txt);
	var hasUc = formValidation.hasUpper(txt);
	var res = formValidation.pwdStrength(txt);
	var resTxt = "";
	var resCss = "";
	
	$(obj.id + '_ps_nr').className = hasNr ? "pwdstr-el pwdstr-el-ok" : "pwdstr-el";			
	$(obj.id + '_ps_an').className = hasAn ? "pwdstr-el pwdstr-el-ok" : "pwdstr-el";			
	$(obj.id + '_ps_lc').className = hasLc ? "pwdstr-el pwdstr-el-ok" : "pwdstr-el";			
	$(obj.id + '_ps_uc').className = hasUc ? "pwdstr-el pwdstr-el-ok" : "pwdstr-el";			
	$(obj.id + '_ps_ln').className = txt.length >= 14 ? "pwdstr-el pwdstr-el-ok" : "pwdstr-el";
	
	switch(res) {
		case 0:
			resTxt = pwdstr_0;
			resTitle = pwdstr_msg_0;
			resCss = "pwdstr-bar pwdstr-bar-0";
			break;
		case 1:
			resTxt = pwdstr_1;
			resTitle = pwdstr_msg_1;
			resCss = "pwdstr-bar pwdstr-bar-1";					
			break;
		case 2:
			resTxt = pwdstr_2;
			resTitle = pwdstr_msg_2;
			resCss = "pwdstr-bar pwdstr-bar-2";					
			break;
		case 3:
			resTxt = pwdstr_3;
			resTitle = pwdstr_msg_3;
			resCss = "pwdstr-bar pwdstr-bar-3";					
			break;					
	}
	var resObj = $(obj.id + '_ps_res');
	resObj.innerHTML = resTxt;
	resObj.className = resCss;
	resObj.title = resTitle; 			
}

function isValidEmail(val) {
   return /^[a-zA-Z0-9]{1,1}[a-zA-Z0-9._-]*@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(val);
}