/*	(c) 2008-2009 Real Estate Data X-Change, Inc.
*	@author Everett Morse
*	
*	JS functions for validating field elements for specific formats.
*	Some of these restrict the input and format on the fly.  Others
*	have an isValidXXX function to use, which will select the text in
*	the element and return false on failure, with an optional parameter
*	to show an alert too (otherwise you must display the alert yourself).
*	
*	Depends on the Prototype.js library.
*	
*	Phone Number:
*	Restricts the field input to a phone number format.  Highlights red if the number is incomplete.
*	- attachPhoneValidator('id_of_element')
*	- isValidPhone(element)
*	- requireValid('id_of_element', isValidPhone, "Error message")
*	
*	Email:
*	Highlights red if the email is invalid
*	- attachEmailValidator('id_of_element')
*	- isValidEmail(element)
*	- requireValid('id_of_element', isValidEmail, "Error message")
*	
*	Numbers:
*	Restricts input to only numbers.  Allows a negative sign and decimal places.
*	- attachNumbersValidator('id_of_element')
*	- require('id_of_element', "Error message")
*/

/**
 * Checks that a required input is not empty.
 * 
 * @param string id - the id of the element to check (or the element itself)
 * @param string msg - message to display if the element's value is empty
 * @return boolean - true if ok, false if not
 */
function require(id, msg) {
	if( $(id).value == '' ) {
		alert(msg);
		$(id).focus();
		return false; 
	} else
		return true;
}

function requireRadio(name, msg) {
	var value = null;
	var firstElem = null;
	$$("input[type='radio']").each(function(elem) {
		if( elem.name == name ) {
			if( !firstElem )
				firstElem = elem;
			if( elem.checked ) {
				value = elem.value;
				throw $break;
			}
		}
	});
	if( value == null ) {
		alert(msg);
		if( firstElem ) firstElem.focus();
		return false;
	} else
		return true;
}

/**
 * Checks that a required input is not empty and that it is valid according to a validation function
 * 
 * @param string id - the id of the element to check (or the element itself)
 * @param function validator - the function (from this file, or in the same format) to use
 * @param string msg - message to display if the element's value is empty
 * @return boolean - true if ok, false if not
 */
function requireValid(id, validator, msg) {
	var elem = $(id);
	if( elem.value == '' || !validator(elem) ) {
		alert(msg);
		elem.focus();
		return false; 
	} else
		return true;
}

/**
 * Checks that an input is valid, but allow for it to be empty
 * 
 * @param string id - the id of the element to check (or the element itself)
 * @param function validator - the function (from this file, or in the same format) to use
 * @param string msg - message to display if the element's value is empty
 * @return boolean - true if ok, false if not
 */
function validate(id, validator, msg) {
	var elem = $(id);
	if( elem.value != '' && !validator(elem) ) {
		alert(msg);
		elem.focus();
		return false; 
	} else
		return true;
}

/********************* PHONE NUMBER ************************/

/**
 * Attach a phone validator to the given text field element
 */
function attachPhoneValidator(element) {
	$(element).observe('keyup', phoneKey);
	$(element).observe('keydown', phoneRestrict);
	$(element).observe('blur', phoneCheck);	//sometimes change events won't trigger, so use blur
}

/**
 * Un-attaches the validator
 */
function removePhoneValidator(element) {
	$(element).stopObserving('keyup', phoneKey);
	$(element).stopObserving('keydown', phoneRestrict);
	$(element).stopObserving('blur', phoneCheck);
}

function phoneCheck(e) {
	var field = e.element();
	var value = field.value.replace(/[^0-9x]/gi,'');	//numbers only
	
	if( value != '' && (value.length < 10 || (value.indexOf('x') > 0 && value.length < 12)) ) {	//must be the right length or blank
		field.style.backgroundColor = '#f66';
	} else
		field.style.backgroundColor = '';	//default
}

/**
 * Attach this to the onKeyUp event of the text area.
 * This will format the field after every key press
 */
function phoneKey(e) {
	//Except don't do this for arrow keys (or home/end) or backspace
	var code = e.keyCode || e.which;
	if( (code >= 35 && code <= 40) || code == 8 )
		return true;
	
	var phoneF = e.element();
	
	var cursor = getCursorInPhoneValue(phoneF);	//may be editing from the middle
	var pos = cursor;
	if( cursor > 0 ) pos += 1;
	if( cursor >= 3 ) pos += 2;
	if( cursor >= 6 ) pos += 1;
	if( cursor > 10 ) pos += 5;
	
	var value = phoneF.value;
	//var pos = 0;
	if( value.replace(/[^0-9]/gi,'').length > 0 ) {	//has at least one number
		var data = format(value);
		value = data[0];
		//pos = data[1];
	} else
		value = "";	//no numbers, so clear field
	
	phoneF.value = value;
	setCursor(phoneF, pos);
}

/**
 * Attach this to the onKeyPress event of the text area.
 * This restricts input to just numbers, keeping the right format.
 * It allows for backspace and tab.  It also will take an "x" or "e"
 * to mean that an extension will be entered.
 */
function phoneRestrict(e) {
	var phoneF = e.element();
	//if( !e ) var e = window.event;	//IE
	var code = e.keyCode || e.which;
	if( code >= 96 && code <= 105 ) code -= 48;//convert numpad chars, which overlap lower case letter ascii values
	var character = String.fromCharCode(code).toLowerCase();	//code will be A-Z even if shift is not pressed
	var value = phoneF.value.replace(/[^0-9x]/gi,'');	//numbers only, and ext.
	var cursor = getCursorInPhoneValue(phoneF);	//may be editing from the middle
	
	//$('debug2').innerHTML = "key code=\""+code+"\" char=\""+character+"\" value=\""+value+"\"";
	
	//Allowed characters: tab, arrows, home/end, CTRL is pressed, function keys, return
	if( code == 9 || (code >= 35 && code <= 40) || e.ctrlKey || (code >= 112 && code <= 123) || code == 13 )
		return true;
	
	if( code == 8 ) { //delete/backspace
		var saveCursorPos = null;
		if( cursor == value.length ) {
			value = value.substring(0,value.length - 1);
		} else {
			value = value.substring(0, cursor - 1) + value.substring(cursor);
			cursor--;
			saveCursorPos = cursor;
			if( cursor > 0 ) saveCursorPos += 1;
			if( cursor >= 3 ) saveCursorPos += 2;
			if( cursor >= 6 ) saveCursorPos += 1;
			if( cursor >= 10 ) saveCursorPos += 1;
		}
		
		var data = format(value);
		phoneF.value = data[0];
		setCursor(phoneF, saveCursorPos != null? saveCursorPos : data[1]);
		
		//phoneCheck(e);	//we're stopping the event, so call this ourselves -- I don't like this behavior, so I changed to using onBlur vs onChange
		e.stop();
	}
	
	if( value.indexOf('x') > 0 ) {
		//Has extension, length must be 10 for number + 1 for x + 1-4 for the ext.
		if( character.replace(/[^0-9]/gi,'') != '' && value.length < 15 )
			return true;	//numeric and there is space left
		else
			e.stop();		//field full or invalid char
	} else {
		//Does not have extension, so max length is 10 (Or 11 if first char is a one, that gets stripped later)
		
		if( value.length >= 10 && (character == 'x' || character == 'e') ) {
			var data = format(value);
			phoneF.value = data[0] + ' ext. ';
			setCursor(phoneF, data[1]+6);
			e.stop();
		} else if( character.replace(/[^0-9]/gi,'') != '' ) {
			if( value.length < 10 || (value.charAt(0) == '1' && value.length < 11) )
				return true;
		}
	}
	e.stop();
}

/**
 * Take an input string and format it to be a phone number like
 * Returns the formatted string and the cursor position to enter the next number.
 * 
 * This uses the national phone number format (vs international) which formats
 * a number like "(800) 555-1234 ext. 123".  The input may start with 1, as in 
 * "1-800-..." and the 1 will be stripped off, but only if there are enough digits.
 * 
 * The first thing format does is to reduce the input to just numbers and the
 * letter 'x'.  So you may use 'x' to separate the extension in input.  Output
 * will use ' ext. ' to separate the extension if there is one.
 * 
 * @param string input - the value from the text field
 * @return [value, where the cursor should be]
 */
function format(input) {
	
	//if there is an extension, pull it off first
	var ext = input.replace(/[^0-9x]/gi,'').split('x');
	if( ext.length == 2 ) {
		input = ext[0];
		ext = ext[1];
	} else {
		input = ext[0];
		ext = null;
	}
	
	if( input.length > 11 )
		input = input.substring(0,11);	//take only 11 chars max
	input = input.split('');	//go thru each character
	var output = '';
	var i = 0;
	var formattingSpaces = 0;
	
	if( input.length == 0 )
		return ['',0];//['(   )    -    ',1];
	
	//Styles:
	//A. (123) 456-7890  (force an area code)
	//B. 1 (234) 567-8900 --> No, let's strip off the 1
	
	if( input.length == 11 && input[0] == '1' ) {	//style B
		//output = '1 ';
		i++;
		//formattingSpaces = 1;	//the space after the one
	}
	output += '(';
	formattingSpaces++;
	
	//Area Code
	if( i < input.length )
		output += input[i++];
	else
		return [output+'   )    -    ', i + formattingSpaces];
	
	if( i < input.length )
		output += input[i++];
	else
		return [output+'  )    -    ', i + formattingSpaces];
		
	if( i < input.length )
		output += input[i++];
	else
		return [output+' )    -    ', i + formattingSpaces];
		
	//First Three
	formattingSpaces += 2;	//paren + space
	if( i < input.length )
		output += ') '+input[i++];
	else
		return [output+')    -    ', i + formattingSpaces];
		
	if( i < input.length )
		output += input[i++];
	else
		return [output+'  -    ', i + formattingSpaces];
		
	if( i < input.length )
		output += input[i++];
	else
		return [output+' -    ', i + formattingSpaces];
	
	//Last Four
	formattingSpaces++;	//dash
	if( i < input.length )
		output += '-'+input[i++];
	else
		return [output+'-    ', i + formattingSpaces];
		
	if( i < input.length )
		output += input[i++];
	else
		return [output+'   ', i + formattingSpaces];
		
	if( i < input.length )
		output += input[i++];
	else
		return [output+'  ', i + formattingSpaces];
		
	if( i < input.length )
		output += input[i++];
	else
		return [output+' ', i + formattingSpaces];
	
	//Ext
	if( ext != null ) {
		output += ' ext. ' + ext;
		i += ext.length;
		formattingSpaces += 6;
	}
	
	return [output, i + formattingSpaces];
}

/**
 * It is more useful to know where in the simplified value the cursor is, meaning without all the spaces, parenthesis, etc.
 * This can be used for editing the value from somewhere in the middle rather than only at the end.
 * @return int
 */
function getCursorInPhoneValue(elem) {
	var before = elem.value.substring(0, getCursor(elem));
	return before.replace(/[^0-9x]/g,'').length;
}

/**
 * Set the cursor position of an element. Works with IE and FF.
 */
function setCursor(elem, pos) {
	if( document.selection ) {	//IE
		var sel = elem.createTextRange();
		sel.collapse(true);
		sel.moveStart("character", pos);
		sel.moveEnd("character", 0);
		sel.select();
	} else {	//FF
		elem.selectionStart = pos;
		elem.selectionEnd = pos;
	}
}

/**
 * Get the position of the caret.
 * See http://www.webdeveloper.com/forum/showthread.php?t=74982
 * 
 * @return int //{ start: ..., end: ... }
*/
function getCursor(elem) {
	//var start = 0;
	var end = 0;
	
	if( document.selection ) {	//IE
		//start = Math.abs(document.selection.createRange().moveStart("character", -elem.value.length));
		end = Math.abs(document.selection.createRange().moveEnd("character", -elem.value.length)); 
	} else {	//FF
		//if( elem.selectionStart || elem.selectionStart == '0' )
		//	start = elem.selectionStart;
		if( elem.selectionEnd || elem.selectionEnd == '0' )
			end = elem.selectionEnd;
	}
	//return { start: start, end: end };
	return end;
}

/**
 * On failure highlight the element and return false.  Optionally show a message.
 * @param element - the text field to validate
 * @param boolean showAlert (Optional) - if true, shows an alert message too
 * @return boolean
 */
function isValidPhone(element, showAlert) {
	var value = element.value.replace(/[^0-9x]/gi,'');
	if( value.indexOf('x') > 0 ) {
		if( value.length >= 12 )	//10 digits and at least 1 extension digit (plus the x)
			return true;
	} else if( value.length >= 10 ) {
		return true;
	}
	
	element.focus();
	if( showAlert )
		alert("Invalid phone number format.  Please correct it and submit again.");
	return false;
}

/*********************** EMAIL ***********************/


/**
 * Attach an email validator to the given text field element.
 * This only checks after the field is changed, and gives an alert if it fails
 */
function attachEmailValidator(element) {
	$(element).observe('change', emailCheck);
}

/**
 * Un-attaches the validator
 */
function removeEmailValidator(element) {
	$(element).stopObserving('change', emailCheck);
}

function emailCheck(e) {
	var field = e.element();
	if( field.value != '' && !isValidEmail(field,false) ) {
		field.style.backgroundColor = '#f66';
	} else
		field.style.backgroundColor = '';	//default
}

/**
 * On failure highlight the element and return false.  Optionally show a message.
 * @param element - the text field to validate
 * @param boolean showAlert (Optional) - if true, shows an alert message too
 * @return boolean
 */
function isValidEmail(element, showAlert) {
	if( element.value.match(/^([-!#$%&\'*+\\.\/0-9=?A-Z^_`a-z{|}~]+)@([-!#$%&\'*+\\\/0-9=?A-Z^_`a-z{|}~]+\.)[-!#$%&\'*+\\.\/0-9=?A-Z^_`a-z{|}~]+$/)) {
		return true;
	} else {
		element.focus();
		if( showAlert )
			alert("Invalid email address.  Please correct it and submit again.");
		return false;
	}
}


/******************** NUMBERS *************************/

/**
 * Attach a phone validator to the given text field element
 */
function attachNumbersValidator(element) {
	$(element).observe('keypress', numbersRestrict);
	$(element).observe('blur', numbersCheckValid);
}

/**
 * Un-attaches the validator
 */
function removeNumbersValidator(element) {
	$(element).stopObserving('keypress', numbersRestrict);
	$(element).stopObserving('blur', numbersCheckValid);
}

/**
 * Holds a hash table of element ids to assigned ranges for elements that have a numbers validator
 */
var numberValidatorRanges = [];
var numberValidatorNextGenId = 1;

/**
 * Set this element to have a range.  Must already have a numbers validator attached.
 * If min or max is null, that direction is unbounded.  If both are null the range is removed.
 */
function setNumberValidatorRange(element, min, max) {
	var elem = $(element);
	if( !elem.id ) {
		//We need an id, so make one up
		elem.id = 'numberValidatorRange' + numberValidatorNextGenId;
		numberValidatorNextGenId++;
	}
	
	if( min == null && max == null ) {
		//Both null, so remove entry
		if( numberValidatorRanges[elem.id] )
			numberValidatorRanges[elem.id] = undefined;
	} else {
		numberValidatorRanges[elem.id] = {
			min: min,
			max: max
		}
		
		numbersCheckValid(elem);
	}
}


/**
 * Attach this to the onKeyPress event of the text area.
 */
function numbersRestrict(e) {
	var field = e.element();
	var code = e.which != null? e.which : e.keyCode;
	if( code == 0 ) return;	//ignore special chars
	var character = String.fromCharCode(code);
	//var value = field.value.replace(/[^0-9-.+]/gi,'');	//numbers only, and +/-/.
	
	if( code == 8 ) return;	//allow backspace
	
	//For +/-/. allow only one of +/- and one . in the string
	if( character == '+' || character == '-' ) {
		if( field.value.indexOf('+') != -1 || field.value.indexOf('-') != -1 )
			e.stop();	//already has one
	}
	if( character == '.' && field.value.indexOf('.') != -1 )
		e.stop();	//already has ones
	
	if( character.replace(/[^0-9-+.]/gi,'') == "" )	//was not numeric
		e.stop();//return false;
}

/**
 * Attach to the onBlur event to make sure the +/-/. chars still give a valid number
 * If not, take the result of parseFloat which chops off the invalid char and anything after.
 * Also, check the range of this element if a range has been set for it.
 */
function numbersCheckValid(e) {
	var field = e.element? e.element() : e;
	var value = parseFloat(field.value);
	
	//Check range
	if( field.id && numberValidatorRanges[field.id] != undefined ) {
		var range = numberValidatorRanges[field.id];
		if( range.min != null && value < range.min )
			value = range.min;
		if( range.max != null && value > range.max )
			value = range.max;
	}
	
	if( field.value && value != field.value ) {
		field.value = value;	//take the good one, drop other chars (shouldn't get NaN b/c of keypress validator)
		fireEvent(field, 'change');
	}
}

function fireEvent(element,event){
    if (document.createEventObject){
        // dispatch for IE
        var evt = document.createEventObject();
        return element.fireEvent('on'+event,evt)
    }
    else{
        // dispatch for firefox + others
        var evt = document.createEvent("HTMLEvents");
        evt.initEvent(event, true, true ); // event type,bubbling,cancelable
        return !element.dispatchEvent(evt);
    }
}

