/*
	Code Computer Love .Net Validation
	- Simple validation framework that works across DOM enabled browsers
	- Will provide accessible and valid source
*/

var cv_validators;				// array for all validators
var cv_summaries;				// array for all summaries
var cv_processed;				// array for all processed validators
var cv_enabled = true;			// enabled flag that dictates if validation is executed on the client
var cv_alertEnabled = false;	// enabled flag for any inline javascript alerts

///
/// Get a document element by its id
///
function cv_getElemById(id)
{
	var elem = null;

	if (document.getElementById)
		elem = document.getElementById(id);
	else
		elem = document.all[id];

	if (elem != null && elem.id == id)
		return elem;
	else
		return null;
}

///
/// Test if we have a valid javascript object
///
function cv_isObject(a)
{
	return ((a && typeof a == 'object') || cv_isFunction(a));
}

///
/// Test if we have a function
///
function cv_isFunction(a)
{
	return (typeof a == 'function');
}

///
/// Test if we have a valid Array object
///
function cv_isArray(a)
{
	return (cv_isObject(a) && a.constructor == Array);
}

///
/// Test if a value is empty i.e. null or length <= 0
///
function cv_isEmpty(a)
{
	return (cv_isNull(a) || a.length <= 0)
}

///
/// Test is an object is null
///
function cv_isNull(a)
{
	return (typeof a == 'object' && !a);
}

///
/// Replace in a string value (v) a string to be found (f) with
/// a new value (r)
///
function cv_replace(v, f, r)
{
	if (cv_isEmpty(v)) return v;
	else return v.replace(f, r);
}

///
/// Code validation alert that can be toggled off/on by global setting
///
function cv_alert(message)
{
	if (cv_alertEnabled)
		alert(message);
}

///
/// Facility for document elements to turn off client validation
/// This is a function call so other properties can be added during
/// the validation framework lifetime
///
function cv_exec(state)
{
	cv_enabled = state;
}

///
/// Code validation on submit event
///
function cv_onSubmit()
{
	// check if any xstandard fields have been rendered, in which case the wysiwyg
	// content needs to be swapped to its hidden field
	if (typeof(window['xstandardFields']) != "undefined") XStandardSubmitHandler();
	
	if (cv_enabled)
		return cv_process();
	else
		return true;
}

///
/// Main validation routine to handle validators and summaries
///
function cv_process()
{
	var i = 0;
	var retVal = true;

	// finish if no validators have been initialised
	if (!cv_isArray(cv_validators)) return true;

	// initialise processed array for all the validators
	cv_processed = new Array(cv_validators.length);

	// loop over all validators and validate
	for (i = 0; i < cv_validators.length; i++)
	{
		var val = cv_validators[i];
		retVal = cv_validate(val);
		cv_processed[i] = val;
	}
	
	var pageIsValid = true;

	// loop over again and check if any are still invalid
	for (i = 0; i < cv_processed.length; i++)
	{
		if (!cv_processed[i].isValid) pageIsValid = false;
	}
	
	if (!pageIsValid)
	{
		// handle summaries
		cv_valSummary();
	}

	return pageIsValid;
}

///
/// Validate a validator instance
///
function cv_validate(val)
{
	if (!val.enabled)
	{
		val.isValid = true;
	}
	else
	{
		// join the validator and form element we are validating
		cv_linkControls(val);

		// if there is an init handler for the validator then call it
		if (val.init) val.init(val);

		// try and validate the control
		if (val.handler)
		{
			// standard validation value is found
			// TODO: look into using checked value and selected index values
			val.valValue = cv_getValidationValue(val, val.objConToVal, val.conToVal, val.conToValType);
			val.isValid = val.handler(val);
		}
		else
		{
			val.isValid = true;
		}

		// set an error message as required
		if (!cv_isNull(val.objErrCon))
		{
			if (!val.isValid)
			{
				// inline render an error message if the display type is static
				if (val.display == 'static')
				{
					if (!cv_isEmpty(val.inlineErrMessage))
					{
						val.objErrCon.innerHTML = val.inlineErrMessage;
						val.objErrCon.className = val.inlineCssClassInvalid;
					}
					else if (!cv_isEmpty(val.errMessage))
					{
						val.objErrCon.innerHTML = val.errMessage;
						val.objErrCon.className = val.inlineCssClassInvalid;
					}
				}

				if (!cv_isNull(val.objConToVal))
				{
					val.objConToSetCss.className = val.cssClassInvalid;
					// lock the control we are validating as if the field has any other
					// validators and they pass validation, we don't want any field css
					// to be wiped off
					val.objConToVal.locked = true;
					val.objConToVal.lockedBy = val.objErrCon.id;
				}
			}
			else
			{
				// Set error container error string to empty
				val.objErrCon.innerHTML = '';
				
				// Reset the locked setting
				if (!cv_isNull(val.objConToVal) && val.objConToVal.locked && val.objConToVal.lockedBy == val.objErrCon.id)
				{
					val.objConToVal.locked = false;
				}
			}
			
			// Not null or locked so change css on control to validate object
			if (!cv_isNull(val.objConToVal) && !val.objConToVal.locked)
			{
				val.objConToSetCss.className = (val.isValid) ? val.cssClassValid : val.cssClassInvalid;
			}
			
			// if we are rendering inline then set the valid css
			// class for the validator container
			if (val.display == 'static')
			{
				val.objErrCon.className = (val.isValid) ? val.inlineCssClassValid : val.inlineCssClassInvalid;
			}

			// see if a post validation handler exists
			if (val.post) val.post(val);
		}
	}

	return true;
}

///
/// Render the validation summaries
///
function cv_valSummary()
{
	// return if no summaries have been initialised
	if (!cv_isArray(cv_summaries)) return false;

	var i = 0;
	for (i = 0; i < cv_summaries.length; i++)
	{
		var sum = cv_summaries[i];
		cv_valSummaryPop(sum);
	}
}

///
/// Populate the validation summary from processed validators
///
function cv_valSummaryPop(sum)
{
	if (!sum.enabled) return false;

	if (cv_processed.length > 0)
	{
		sum.objContainer = cv_getElemById(sum.container);
		if (cv_isNull(sum.objContainer)) return false;

		var hs;	// header separator
		var gp;	// group prefix
		var vp;	// value prefix
		var vs;	// value suffix
		var gs;	// group suffix

		if (sum.dispMode == 'list')
		{
			hs = '<br />'; gp = ''; vp = '';
			vs = '<br />'; gs = '';
		}
		else if (sum.dispMode == 'singleparagraph')
		{
			hs = ' '; gp = ''; vp = '';
			vs = ' '; gs = '';
		}
		else
		{
			hs = ''; gp = '<ul>'; vp = '<li>';
			vs = '</li>'; gs = '</ul>';
		}
	
		var s = '';	// final summary string

		if (!cv_isEmpty(sum.headerText))
		{
			s += sum.headerText;
			s += hs;
		}

		s += gp;

		var i = 0;
		for (i = 0; i < cv_processed.length; i++)
		{
			var val = cv_processed[i];
			if (!val.isValid && !cv_isEmpty(val.errMessage))
			{
				s += vp + val.errMessage + vs;
			}
		}

		s += gs;

		// set the summary if required
		if (!cv_isEmpty(s))
			sum.objContainer.innerHTML = s;

		// optional focus to summary once complete
		// nb. the last executed summary with this property enabled
		// will be retain browser focus
		if (sum.focusOnErr)
		{
			cv_focusElem(sum.container);
		}
	}
}

///
/// Focus an element
///
function cv_focusElem(anchorTitle)
{
	if (anchorTitle.indexOf('#') == -1)
		anchorTitle = "#" + anchorTitle;

	location.hash = anchorTitle;
}

///
/// Bind the validator to the HTML element we are validating and the error container
/// For each "val" we set a reference to the control which will have it's css modified.
/// This is because the control requiring css change may be different to the control we
/// are validating. This is the case for list controls with "Other value" fields
///
function cv_linkControls(val)
{
	if (val.conToValType == 'codefileupload')
	{
		// Special case when validating a file control
		val.objConToVal = cv_getElemById(val.conToVal + '_InputFile');
		val.objConToSetCss = val.objConToVal;
	}
	else if (val.conToValType == 'codedropdownlist' || val.conToValType == 'codecheckboxlist' || val.conToValType == 'coderadiobuttonlist')
	{
		// Special case when validating a drop down list.
		// An additional "other" field could exist so try and map the control to validate
		// instance against the other field first and only if it is not there is the value is
		// empty, map the actual list control as normal.
		val.objConToVal = cv_getElemById(val.conToVal + '_OtherValueField');
		val.objConToSetCss = cv_getElemById(val.conToVal);
		if (!cv_isNull(val.objConToVal))
		{
			if (cv_isEmpty(val.objConToVal.value))
			{
				val.objConToVal = cv_getElemById(val.conToVal);
			}
			else
			{
				val.conToValType == 'codetextbox';
			}
		}
		else
		{
			val.objConToVal = cv_getElemById(val.conToVal);
		}
	}
	else
	{
		val.objConToVal = cv_getElemById(val.conToVal);
		val.objConToSetCss = val.objConToVal;
	}
	
	val.objErrCon = cv_getElemById(val.errContainer);
}

///
/// Get the checked validation value
///
function cv_getCheckedValidationValue(field)
{
	if (!cv_isNull(field))
	{
		if (field.type == 'checkbox' || field.type == 'radio')
		{
			return field.checked;
		}
	}

	return null;
}

///
/// Get the selected index validation value
///
function cv_getSelIndexValidationValue(field)
{
	if (!cv_isNull(field))
	{
		if (field.type == 'select')
		{
			return field.selectedIndex;
		}
	}

	return null;
}

///
/// Get the validation value given an object
///
function cv_getValidationValue(val, field, conToVal, specialType)
{
	if (!cv_isNull(field))
	{
		if (field.type == 'text' || field.type == 'textarea' || field.type == 'hidden' || (field.type == 'checkbox' && specialType != 'codecheckbox') || field.type == 'radio' || field.type == 'file' || field.type == 'password')
		{
			return field.value;
		}
		else if (field.type == 'select' || field.type == 'select-one')
		{
			if (field.options.length == 0) return '';
			else if (field.selectedIndex == -1) return '';
			else return field.options[field.selectedIndex].value;
		}
	}
	
	if (!cv_isNull(specialType))
	{
		if (specialType == 'codecheckbox')
		{
			// all checkbox's on their own will output a value of 'true'
			// but we need to go off the checked property for client side
			if (field.checked) return 'true';
			else return '';
		}
		else if (specialType == 'codecheckboxlist' || specialType == 'checkboxlist' ||
			specialType == 'coderadiobuttonlist' || specialType == 'radiobuttonlist')
		{
			// special case to find list of checkbox's or radio buttons
			// it assumes a set naming pattern of <id>_<count>
			var count = 0;
			var item = cv_getElemById(conToVal + '_' + count);
			while (item != null)
			{
				if (item.checked) return item.value;
				count++;
				item = cv_getElemById(conToVal + '_' + count);
			}
		}
	}

	return null;
}

///
/// Given the c# RegExp options, get the javascript equivalents
///
function cv_regExpOptions(o)
{
	// g stands for global search and is used whatever the options are
	if (o == 'ignorecase')
		return 'gi';
	else if (o == 'multiline')
		return 'gm';
	else
		return 'g';
}

///
/// Parse the numeric part of a string i.e. 5 in 00005
///
function cv_parseInt(v)
{
	while (v.substring(0, 1) == '0')
		v = v.substring(1);
	return parseInt(v);
}

///
/// Default handler that should not used
///
function cv_handler(val)
{
	cv_alert("cv_handler should not be used - see documentation");
	return true;
}

///
/// Required field handler - does not use initial value
///
function cv_reqFldHandler(val)
{
	// special case for a codetextbox - we assume that as the valValue is null
	// the file input is not visible and the form needs to pass validation
	if (val.valValue == null && val.conToValType == 'codefileupload') return true;
	
	if (cv_isEmpty(val.valValue)) return false;
	return !(val.valValue == val.initValue);
}

///
/// Float handler
///
function cv_floatHandler(val)
{
	// basic changes to error message
	cv_numErrMessage(val);
	
	if (val.allowEmpty && cv_isEmpty(val.valValue)) return true;
	if (!val.allowEmpty && cv_isEmpty(val.valValue)) return false;

	// take a copy of the validation value
	var v = val.valValue;
	
	if (v == '') return false;
	
	var exp = null;
	if (val.allowThousands)
		exp = new RegExp('^\\-?\\(?([0-9]{0,3}(\\' + val.numCulture.numGrpSep + '?[0-9]{3})*(\\' + val.numCulture.numDecSep + '?[0-9]*))\\)?$');
	else
		exp = new RegExp('^\\-?\\(?([0-9]*(\\' + val.numCulture.numDecSep + '?[0-9]*))\\)?$');
	
	var m = v.match(exp);
	if (m == null) return false;
    
	// replace all occurences of the group separator
	v = v.replace(new RegExp('\\' + val.numCulture.numGrpSep, 'g'), '');
	// replace the decimal separator (there should be only one)
	v = v.replace(val.numCulture.numDecSep, '.');
	
	var fv = parseFloat(v);
	
	if (isNaN(fv)) return false;
	else if (val.rangeType == 'none') return true;
	else if (val.rangeType == 'ranged' && (
		(fv >= val.minValue && fv <= val.maxValue && val.inclusive) || 
		(fv > val.minValue && fv < val.maxValue && !val.inclusive)))
		return true;
	else if (val.rangeType == 'min' && (
		(fv >= val.minValue && val.inclusive) || 
		(fv > val.minValue && !val.inclusive)))
		return true;
	else if (val.rangeType == 'max' && (
		(fv <= val.maxValue && val.inclusive) ||
		(fv < val.maxValue && !val.inclusive)))
		return true;
	else if (val.rangeType == 'equal' && fv == val.minValue && fv == val.maxValue)
		return true;
	else
		return false;

	return true;
}

///
/// Integer handler
///
function cv_integerHandler(val)
{
	// basic changes to error message
	cv_numErrMessage(val);
	
	if (val.allowEmpty && cv_isEmpty(val.valValue)) return true;
	if (!val.allowEmpty && cv_isEmpty(val.valValue)) return false;

	// take a copy of the validation value
	var v = val.valValue;
	
	if (v == '') return false;
	
	var exp = null;
	if (val.allowThousands)
		exp = new RegExp('^\\-?[0-9]{0,3}(\\' + val.numCulture.numGrpSep + '?[0-9]{3})*$');
	else
		exp = /^[\+\-]?[0-9]*$/;
	
	var m = v.match(exp);
	if (m == null) return false;
    
	// replace all occurences of group separator
	v = v.replace(new RegExp('\\' + val.numCulture.numGrpSep, 'g'), '');
    
	var iv = parseInt(v);
	
	if (isNaN(iv)) return false;
	else if (val.rangeType == 'none') return true;
	else if (val.rangeType == 'ranged' && (
		(iv >= val.minValue && iv <= val.maxValue && val.inclusive) || 
		(iv > val.minValue && iv < val.maxValue && !val.inclusive)))
		return true;
	else if (val.rangeType == 'min' && (
		(iv >= val.minValue && val.inclusive) || 
		(iv > val.minValue && !val.inclusive)))
		return true;
	else if (val.rangeType == 'max' && (
		(iv <= val.maxValue && val.inclusive) ||
		(iv < val.maxValue && !val.inclusive)))
		return true;
	else if (val.rangeType == 'equal' && iv == val.minValue && iv == val.maxValue)
		return true;
	else
		return false;

	return true;
}

///
/// Add group separators to a value
/// eg. 99999999.99 = 99,999,999.99
///
function cv_numAddSep(v, decSep, grpSep)
{
	v += '';
	var x = v.split(decSep);
	x1 = x[0];
	x2 = x.length > 1 ? decSep + x[1] : '';
	var rgx = /(\d+)(\d{3})/;
	while (rgx.test(x1))
	{
		x1 = x1.replace(rgx, '$1' + grpSep + '$2');
	}
	return x1 + x2;
}

///
/// Globalise a numeric value
/// eg. 12345.11 with culture de-DE is 12345,11 but the only way to set the number in
/// .net is 12345.11
///
function cv_numGlobal(v, decSep)
{
	v += '';
	return v.replace('.', decSep);
}

///
/// Manipulate an error message for a numeric validator
///
function cv_numErrMessage(val)
{
	var em = val.errMessage; var iem = val.inlineErrMessage; var rt = val.rangeType;
	var min = val.minValue; var max = val.maxValue;
	var ds = val.numCulture.numDecSep; var gs = val.numCulture.numGrpSep;
	
	if (!cv_isEmpty(em))
	{
		if (em.indexOf('{0') != -1 && em.indexOf('{1') && (rt == 'ranged' || rt == 'equal'))
		{
			if (val.allowThousands)
			{
				em = cv_replace(em, '{0:n}', cv_numAddSep(cv_numGlobal(min, ds), ds, gs));
				em = cv_replace(em, '{1:n}', cv_numAddSep(cv_numGlobal(max, ds), ds, gs));
			}
			em = cv_replace(em, '{0}', cv_numGlobal(min, ds));
			em = cv_replace(em, '{1}', cv_numGlobal(max, ds));
		}
		else if (em.indexOf('{0') != -1 && rt == 'min')
		{
			if (val.allowThousands) em = cv_replace(em, '{0:n}', cv_numAddSep(cv_numGlobal(min, ds), ds, gs));
			em = cv_replace(em, '{0}', cv_numGlobal(min, ds));
		}
		else if (em.indexOf('{0') != -1 && rt == 'max')
		{
			if (val.allowThousands) em = cv_replace(em, '{0:n}', cv_numAddSep(cv_numGlobal(max, ds), ds, gs));
			em = cv_replace(em, '{0}', cv_numGlobal(max, ds));
		}
		val.errMessage = em;
	}

	if (!cv_isEmpty(iem))
	{
		if (iem.indexOf('{0') != -1 && iem.indexOf('{1') && (rt == 'ranged' || rt == 'equal'))
		{
			if (val.allowThousands)
			{
				iem = cv_replace(iem, '{0:n}', cv_numAddSep(cv_numGlobal(min, ds), ds, gs));
				iem = cv_replace(iem, '{1:n}', cv_numAddSep(cv_numGlobal(max, ds), ds, gs));
			}
			iem = cv_replace(iem, '{0}', cv_numGlobal(min, ds));
			iem = cv_replace(iem, '{1}', cv_numGlobal(max, ds));
		}
		else if (iem.indexOf('{0') != -1 && rt == 'min')
		{
			if (val.allowThousands) iem = cv_replace(iem, '{0:n}', cv_numAddSep(cv_numGlobal(min, ds), ds, gs));
			iem = cv_replace(iem, '{0}', cv_numGlobal(min, ds));
		}
		else if (iem.indexOf('{0') != -1 && rt == 'max')
		{
			if (val.allowThousands) iem = cv_replace(iem, '{0:n}', cv_numAddSep(cv_numGlobal(max, ds), ds, gs));
			iem = cv_replace(iem, '{0}', cv_numGlobal(max, ds));
		}
		val.inlineErrMessage = iem;
	}
}

///
/// Regular Expression handler
///
function cv_regExpHandler(val)
{
	if (!cv_isNull(val.pattern))
	{
		if (val.allowEmpty && cv_isEmpty(val.valValue))
		{
			return true;
		}

		var re = new RegExp(val.pattern, cv_regExpOptions(val.options));
		return re.test(val.valValue);
	}

	return true;
}

///
/// Compare handler .. yet to complete
///
function cv_compareHandler(val)
{
	return true;
}

///
/// Match validator init handler
///
function cv_matchInitHandler(val)
{
	val.objConToMatch = cv_getElemById(val.conToMatch);
	val.matchValue = cv_getValidationValue(val, val.objConToMatch, val.conToMatch, val.conToMatchType);
}

///
/// Match handler
///
function cv_matchHandler(val)
{
	var text = val.valValue;
	var compareTo = val.matchValue;

	if (val.allowEmpty && cv_isEmpty(text) && cv_isEmpty(compareTo))
	{
		return true;
	}
	else if (!val.allowEmpty && (cv_isEmpty(text) || cv_isEmpty(compareTo)))
	{
		return false;
	}
	else
	{
		if (val.ignoreCase)
		{
			text = text.toLowerCase();
			compareTo = text.toLowerCase();
		}

		return (text == compareTo);
	}
}

///
/// Post validation handler
///
function cv_matchPostHandler(val)
{
	if (cv_isNull(val.objConToMatch)) return;
	if (!val.isValid)
	{
		if (!cv_isNull(val.objConToMatch))
		{
			val.objConToMatch.className = val.cssClassInvalid;
			// lock the control we are validating as if the field has any other
			// validators and they pass validation, we don't want any field css
			// to be wiped off
			val.objConToMatch.locked = true;
			val.objConToMatch.lockedBy = val.objErrCon.id;
		}
	}
	else
	{
		// Reset the locked setting
		if (!cv_isNull(val.objConToMatch) && val.objConToMatch.locked && val.objConToMatch.lockedBy == val.objErrCon.id)
		{
			val.objConToMatch.locked = false;
		}
	}
	
	if (!cv_isNull(val.objConToMatch) && !val.objConToMatch.locked)
	{
		val.objConToMatch.className = (val.isValid) ? val.cssClassValid : val.cssClassInvalid;
	}
}

///
/// Try and parse a date string given a string value (v), format
/// string and date time culture object. It will return an array
/// of three elements, day, month and year as numeric values. Javascript's
/// date implementation is not culture aware.
///
function cv_parseDate(v, format, dtCulture)
{
	if (cv_isNull(v)) return null;
	if (!format) format = 'dd/mm/yyyy';
	
	v = v.toLowerCase();
	format = format.toLowerCase();
	
	// try and split the value on the culture separator
	var vs = cv_splitDateParts(v, dtCulture.sep);
	if (vs.length != 3) return null;
	
	// try and split the format string on the culture separator
	var fs = cv_splitDateParts(format, dtCulture.sep);
	if (fs.length != 3) return null;
	
	// build up a new version of the value with culture separator
	v = '';
	for (var i = 0; i < vs.length; i++)
	{
		if (vs[i] != '')
		{
			if (v != '') v += dtCulture.sep;
			v += vs[i];
		}
	}
	
	// split again to guarantee there are 3 parts
	vs = cv_splitDateParts(v, dtCulture.sep);
	if (vs.length != 3) return null;
	
	var d = vs[0]; var fd = fs[0];
	var m = vs[1]; var fm = fs[1];
	var y = vs[2]; var fy = fs[2];
	
	// determine the date part order from the format string
	if (format.indexOf('y') < format.indexOf('m'))
	{
		// year month day
		y = vs[0]; fy = fs[0];
		m = vs[1]; fm = fs[1];
		d = vs[2]; fd = fs[2];
	}
	else if (format.indexOf('m') < format.indexOf('d'))
	{
		// month day year
		m = vs[0]; fm = fs[0];
		d = vs[1]; fd = fs[1];
	}
	else
	{
		// day month year - nothing to do
	}
	
	// try and parse the actual month name
	if (fm.length == 3)
		m = cv_getMonth(m, dtCulture.mabr)
	else if (fm.length == 4)
		m = cv_getMonth(m, dtCulture.m)
	
	// get the full year yyyy
	if (y.length == 2)
	{
		if (parseFloat(y) > 40) y = '19' + y;
		else y = '20' + y;
	}
	
	// pad month and day if required
	if (parseInt(m) < 10 && m.length < 2) m = '0' + m;
	if (parseInt(d) < 10 && d.length < 2) d = '0' + d;
	
	return new Array(parseInt(d,10), parseInt(m,10), parseInt(y,10));
}

///
/// Try and split a date for a given separator
/// Also try other common ones if the first fails
///
function cv_splitDateParts(v, sep)
{
	var r = v.split(sep);
	if (r.length != 3) r = v.split('/');
	if (r.length != 3) r = v.split('.');
	if (r.length != 3) r = v.split('-');
	if (r.length != 3) r = v.split(new RegExp('\\s{1,}', 'gi'));
	if (r.length == 3)
	{
		for (var i = 0; i < r.length; i++)
			r[i] = cv_noWhitespace(r[i]);
	}
	return r;
}

///
/// Get a month name from a month name array (mn)
///
function cv_getMonth(v, mn)
{
	for (var i = 0; i < mn.length; i++)
	{
		if (mn[i].toLowerCase() == v)
		{
			v = (i + 1) + '';
			break;
		}
	}
	return v;
}

///
/// Get the month name given a date object, month format string and culture
///
function cv_getMonthN(d, m, dtCulture)
{
	if (m.length == 3)
		return dtCulture.mabr[d.getMonth()];
	else if (m.length == 4)
		return dtCulture.m[d.getMonth()];
	else
		return ((d.getMonth() + 1) < 10) ? '0' + (d.getMonth() + 1) : (d.getMonth() + 1);
}

///
/// See if a date object can be formatted so that it matches the value
/// input by the user. return an array of values: 
///		val1 = true/false depending on value, v, matching the format string version of the date parts.
///		val2 = formatted date string of value, v.
///		val3 = formatted date string using format string.
///
function cv_matchFmtDate(d, v, format, dtCulture)
{
	format = format.toLowerCase();
	
	// try and split the format string on the culture separator
	var fs = cv_splitDateParts(format, dtCulture.sep);
	if (fs.length != 3) return new Array(false, '', '');
	
	// establish the separator
	var sep = '';
	if (format.indexOf(dtCulture.sep) != -1) sep = dtCulture.sep;
	else if (format.indexOf('/') != -1) sep = '/';
	else if (format.indexOf('.') != -1) sep = '.';
	else if (format.indexOf('-') != -1) sep = '-';
	else if (format.indexOf(' ') != -1) sep = ' ';
	if (cv_isEmpty(sep)) return new Array(false, '', '');
	
	// remove any spaces in the value we are checking so long as we are
	// not using a space as a separator
	if (sep != ' ') v = v.replace(new RegExp('\\s{1,}', 'gi'), '');
	
	var ds = '';
	var day = (d.getDate() < 10) ? '0' + d.getDate() : d.getDate() + '';
	
	if (format.indexOf('y') < format.indexOf('m'))
	{
		// year month day
		ds = d.getFullYear() + sep;
		ds += cv_getMonthN(d, fs[1], dtCulture) + sep;
		ds += day;
	}
	else if (format.indexOf('m') < format.indexOf('d'))
	{
		// month day year
		ds = cv_getMonthN(d, fs[1], dtCulture) + sep;
		ds += day + sep;
		ds += d.getFullYear();
	}
	else
	{
		// day month year
		ds = day + sep;
		ds += cv_getMonthN(d, fs[1], dtCulture) + sep;
		ds += d.getFullYear();
	}
	
	v = v.replace(new RegExp('\\s{1,}', 'gi'), ' ');
	
	return new Array(v.toLowerCase() == ds.toLowerCase(), v.toLowerCase(), ds.toLowerCase());
}

///
/// Remove all whitespace from a string
///
function cv_noWhitespace(v)
{
	var exp = new RegExp('\\s{1,}', 'gi');
	return v.replace(exp, '');
}

///
/// Simple date handler. 
/// Supports the following formats:
///		dd - day (01)
///		mm - month (01)
///		mmm - month (Jan|jan)
///		mmmm - month (January|january)
///		yyyy - year (2006)
/// Any separator can be used and it will try to validate it
///
function cv_simpleDateHandler(val)
{
	// basic changes to error message
	cv_dtErrMessage(val);

	if (val.allowEmpty && cv_isEmpty(val.valValue)) return true;
	
	// collect the date parts from the validation value
	var dp = cv_parseDate(val.valValue, val.format, val.dtCulture);
	
	// failed if not an array or length is not 3
	if (!cv_isArray(dp) || dp.length != 3) return false;
	
	// construct a date
	var d = new Date(dp[2], (dp[1] - 1), dp[0]);
	
	// check the month of the date is the same as the month date part
	// this is because the javascript date object will round over the month
	// so it becomes a valid date
	if (d.getMonth() != (dp[1] - 1)) return false;
	
	// check the day type
	if (val.dayType == 'weekend')
	{
		if (d.getDay() != 0 && d.getDay() != 6) return false;
	}
	else if (val.dayType == 'weekday')
	{
		if (d.getDay() == 0 || d.getDay() == 6) return false;
	}
	
	// try and match the values
	var match = cv_matchFmtDate(d, val.valValue, val.format, val.dtCulture);
	
	// check we have a valid array and match
	if (!cv_isArray(match) || !match[0]) return false;
	else
	{
		if (val.rangeType == 'none') return true;
		else
		{
			// try and parse the min and max dates
			var dtMin = cv_parseDate(val.minValue, val.format, val.dtCulture);
			var dtMax = cv_parseDate(val.maxValue, val.format, val.dtCulture);
			
			if (val.rangeType == 'ranged')
			{
				// assume as it is ranged that the min and max values will always
				// resolve to a date part array as they will have been parsed by .net
				dtMin = new Date(dtMin[2], (dtMin[1] - 1), dtMin[0]);
				dtMax = new Date(dtMax[2], (dtMax[1] - 1), dtMax[0]);
				if ((d >= dtMin && d <= dtMax && val.inclusive) || 
					(d > dtMin && d < dtMax && !val.inclusive))
					return true;
			}
			else if (val.rangeType == 'min')
			{
				dtMin = new Date(dtMin[2], (dtMin[1] - 1), dtMin[0]);
				if ((d >= dtMin && val.inclusive) || 
					(d > dtMin && !val.inclusive))
					return true;
			}
			else if (val.rangeType == 'max')
			{
				dtMax = new Date(dtMax[2], (dtMax[1] - 1), dtMax[0]);
				if ((d <= dtMax && val.inclusive) ||
					(d < dtMax && !val.inclusive))
					return true;
			}
			else
			{
				return false;
			}
		}
	}
}

///
/// Manipulate an error message for a date time validator
///
function cv_dtErrMessage(val)
{
	var em = val.errMessage; var iem = val.inlineErrMessage; var rt = val.rangeType;
	var min = val.minValue; var max = val.maxValue;
	
	if (!cv_isEmpty(em))
	{
		if (em.indexOf('{0') != -1 && em.indexOf('{1') && rt == 'ranged')
		{
			em = cv_replace(em, '{0}', min);
			em = cv_replace(em, '{1}', max);
		}
		else if (em.indexOf('{0') != -1 && rt == 'min')
		{
			em = cv_replace(em, '{0}', min);
		}
		else if (em.indexOf('{0') != -1 && rt == 'max')
		{
			em = cv_replace(em, '{0}', max);
		}
		val.errMessage = em;
	}

	if (!cv_isEmpty(iem))
	{
		if (iem.indexOf('{0') != -1 && iem.indexOf('{1') && rt == 'ranged')
		{
			iem = cv_replace(iem, '{0}', min);
			iem = cv_replace(iem, '{1}', max);
		}
		else if (iem.indexOf('{0') != -1 && rt == 'min')
		{
			iem = cv_replace(iem, '{0}', min);
		}
		else if (iem.indexOf('{0') != -1 && rt == 'max')
		{
			iem = cv_replace(iem, '{0}', max);
		}
		val.inlineErrMessage = iem;
	}
}
