/** FormCheck Class
 *  
 *  @param object config - config which generates php class FormCheck with GetJsConfig function (PEAR-JSON class should be included)
 *  @author Monin Dmitry
 *  @version 1.1.0.0
 */

(function() {
var that;

var zipcodesRegexp = {
		'ar':'/^[B-T]{1}\\d{4}[A-Z]{3}$/i',
        'at':'/^[0-9]{4}$/i',
        'au':'/^[2-9][0-9]{2,3}$/i',
        'be':'/^[1-9][0-9]{3}$/i',
        'ca':'/^[a-z][0-9][a-z][ \t-]*[0-9][a-z][0-9]$/i',
        'ch':'/^[0-9]{4}$/i',
        'cn':'/^[0-9]{6}$/',
        'de':'/^[0-9]{5}$/i',
        'dk':'/^(DK-)?[0-9]{4}$/i',
        'ee':'/^[0-9]{5}$/',
        'es':'/^[0-4][0-9]{4}$/',
        'fi':'/^(FI-)?[0-9]{5}$/i',
        'fr':'/^(0[1-9]|[1-9][0-9])[0-9][0-9][0-9]$/i',
        'in':'/^[1-9]{1}[0-9]{2}(\\s|\\-)?[0-9]{3}$/i',
        'it':'/^[0-9]{5}$/',
        'is':'/^[0-9]{3}$/',
        'lv':'/^(LV-)?[1-9][0-9]{3}$/i',
        'mx':'/^[0-9]{5}$/',
        'nl':'/^[0-9]{4}.?[a-z]{2}$/i',
        'no':'/^[0-9]{4}$/',
        'nz':'/^[0-9]{4}$/',
        'pl':'/^[0-9]{2}-[0-9]{3}$/',
        'pt':'/^[0-9]{4}-[0-9]{3}$/',
        'ru':'/^[0-9]{6}$/',
        'se':'/^[0-9]{3}\\s?[0-9]{2}$/',
        'tr':'/^[0-9]{5}$/',
        'uk':'/^[a-z][a-z0-9]{1,3}\\s?[0-9][a-z]{2}$/i',
        'us':'/^[0-9]{5}((-| )[0-9]{4})?$/'
};

function GetFieldErrors(fieldName, value)
{
	var errors = [];
	var isError, options;
	for(var validationKey in this.config.validations[fieldName])
	{
		options = this.config.validations[fieldName][validationKey];
		isError = false;
		
		if(value && value.length == 0 && validationKey != "empty")
		{
			continue;
		}
				
		try
		{
			switch(validationKey)
			{
				case "empty":
					isError = that.IsEmpty(value);
					break;
				case "equals":
					isError = !that.Equals(value, options);
					break;
				case "email":
					isError = !that.IsEmail(value);
					break;
				case "zipcode":
					isError = !that.IsZipcode(value, options);
					break;
				case "regexp":
					isError = !that.MatchesRegExp(value, options);
					break;
				case "date":
					isError = !that.IsDate(value);
					break;
				case "birthdate":
					isError = !that.IsBirthdate(value);
					break;
				default:
					isError = false;
					break;
			}
		}
		catch(e)
		{
			throw "couldn't check " + fieldName;
		}
				
		if (isError) 
		{
			errors[errors.length] = validationKey;
		}			
	}
	
	return errors;
}



FormCheck = function(config)
{
	
	this.config = config;
	that = this;
	
	
}

FormCheck.prototype = {
	formVars : null,
	errors : {},
	/** void Check(object formVars)
	 *  Checks form variables and creates array with form errors
	 *  @param object formVars - object with form variables
	 */
	Check : function(formVars)
	{
		this.formVars = formVars;
		this.errors = {};
		var fieldValue = "";
		var fieldName = null, fieldErrors;
		for(var fieldName in this.config.validations)
		{
			fieldValue = typeof(formVars[fieldName] == "string" || formVars[fieldName] == "number") ? formVars[fieldName] : "";
			fieldErrors = GetFieldErrors.call(this, fieldName, fieldValue);
			
			for (var i = 0; i < fieldErrors.length; i++) 
			{
				this.RegisterError(fieldName, fieldErrors[i]);
			}
		}
		
		return {
			success 	: !this.HasErrors(),
			errors		: this.errors
		};
	},
	/** void CheckField(fieldName, fieldValue)
	 *  Checks specified field
	 *  @param string fieldName - name of field to check
	 *  @param string fieldValue - value
	 *  @return object result - result has 2 properties: success (true, false), errors : array with errors
	 */
	CheckField : function(fieldName, fieldValue)
	{
		var fieldErrors = GetFieldErrors.call(this, fieldName, fieldValue);
		
		return {
			success 	: (fieldErrors.length == 0),
			errors		: fieldErrors
		};
	},
	/** Returns true if value = options.field
	 *  @return bool
	 */
	Equals : function(value, options)
	{
		return (value == this.formVars[options.field]);
	},
	/** Returns error text for specified field and validation
	 * 
	 * @param string field - field name
	 * @param string validation - validation name
	 */
	GetErrorText : function(field, validation)
	{
		if(!this.config.errorTexts.hasOwnProperty(field))
		{
			throw "Please specify error text for field " + field;
		}
				
		if(validation && !this.config.errorTexts[field].hasOwnProperty(validation))
		{
			throw "Please specify error text for field " + field + " : " + validation;
		}
		
		if(!validation)
		{
			for (var val in this.errors[field]) 
			{
				validation = val;
				break;
			}
		}
		
		return this.config.errorTexts[field][validation];
	},
	/** Returns indexed array with errors, should be used after Check() method
	 *  @returns string[]
	 */
	GetErrorTexts : function()
	{
		var errorTexts = [];
		for(var field in this.errors)
		{
			errorTexts[errorTexts.length] = this.GetErrorText(field);
			
		}
		return errorTexts;
	},
	/** Returns associative array with errors in following format
     *  ARRAY[FIELD_KEY] = FIELD_ERROR
     *  @return array
     */
	GetErrorTextsAssoc : function()
	{
		var errorTexts = {};
		for(var field in this.errors)
		{
			for(var validation in this.errors[field])
			{
				errorTexts[field] = this.GetErrorText(field, validation);
				break;
			}
		}
		return errorTexts;
	},
	/** void RegisterError(string fieldName, string errorType)
	 *  Registers an error
	 *  @param string fieldName - field name
	 *  @param string errorType - error type, i.e. empty, regexp and all other custom defined errors
	 */
	RegisterError: function(fieldName, errorType)
	{
		var arr = new Array();
		if( typeof(this.errors[fieldName]) != "object" )
		{
			this.errors[fieldName] = {};
		}
		
		this.errors[fieldName][errorType] = true;
	},
	/** bool IsEmpty(string value)
	 *  Returns true if value is empty
	 *  @param string value
	 *  @return bool
	 */
	IsEmpty : function(value)
	{
		if(typeof(value) == "undefined")
		{
			return true;
		}
		
		if(typeof(value) != "string")
		{
			throw "IsEmpty: Value should be a string, value: " + typeof(value);
		}
		return (typeof(value) == "string" && value.length == 0);	
	},
	/** bool IsEmail(string value)
	 *  Checks value with e-mail regexp pattern
	 *  @param string value
	 *  @return bool
	 */
	IsEmail : function(value)
	{
		var emailPattern = "/^[A-Za-z0-9._%-]+@[A-Za-z0-9._%-]+\.[A-Za-z]{2,6}$/i";
		return this.MatchesRegExp(value, {pattern: emailPattern});
	},
	
	/** bool IsZipcode(string value)
	 *  Check value with zipcode pattern
	 *  @param string value
	 *  @return bool
	 */
	IsZipcode : function(value, options)
	{
		var country = (options.countries) ? options.countries : "*";
		
		if(country != "*")
		{
			var countries = country.split(",");
			for(var i = 0; i < countries.length; i++)
			{
				
				if(this.MatchesRegExp(value, {pattern: zipcodesRegexp[countries[i]]}))
				{
					return true;
				}
			}
			return false;
		}
		else
		{
			for(var i in zipcodesRegexp)
			{
				if(this.MatchesRegExp(value, {pattern: zipcodesRegexp[i]}))
					return true;
			}
			return false;
		}
	},
	/** bool MatchesRegExp(string pattern, string value)
	 * 
	 *  @param string pattern - pattern in perl regexp format /pattern/modifiers (i.e. /[0-9]+/ig)
	 *  @param string value
	 *  @return bool
	 */
	MatchesRegExp: function(value, options)
	{
		pattern = options.pattern;
		var parts = pattern.split("/");
		pattern = "";
		for(var i = 1; i < parts.length-1; i++)
		{
			pattern += parts[i];
		}
		var flags = parts[parts.length - 1];
				
		var re = new RegExp(pattern, flags);
		
		return re.test(value);
	},
	/** bool IsDate(string value)
	 *  Checks if date is correct, returns true if yes
	 *  @param string value - date in format YYYY-MM-DD
	 *  @return bool
	 */
	IsDate : function(value)
	{
		if(!that.MatchesRegExp(value, { pattern: "/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/" }))
		{
			return false;
		}
		
		var dateArr = value.split("-");
		var dateObj = new Date(dateArr[0], dateArr[1] - 1, dateArr[2]);
		
		return ( (Number)(dateObj.getDate()) == (Number)(dateArr[2]) 
					&& ((Number)(dateObj.getMonth()) + 1) == (Number)(dateArr[1])  );
	},
	/** bool IsDate(string value)
	 *  Checks if date is correct, returns true if yes
	 *  @param string value - date in format YYYY-MM-DD
	 *  @return bool
	 */
	IsBirthdate : function(value)
	{
		if(!that.MatchesRegExp(value, {pattern: "/^[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}$/"}))
		{
			return false;
		}
		if(!this.IsDate(value))
		{
			return false;
		}
		var dateArr = value.split("-");
		var year = (Number)(dateArr[0]);
		var now = new Date();
		var currentYear = now.getFullYear();
		
		return (year <= currentYear && year > currentYear - 100);
	},
	/** bool HasErrors()
	 *  after checking the form returns true if form has errors and false if not.
	 *  @return bool
	 */
	HasErrors: function()
	{
		var i = 0;
		for(var err in this.errors)
		{
			i++;
		}
		return (i > 0);
	},
	/** object GetFormVars(string/object formId)
	 *  Returns an object with form variables. 
	 *  @param string/object formId - form id or form object
	 */
	GetFormVars : function (formId, isAspNet)
	{
		var oForm;
		if(typeof formId == 'string'){
			// Determine if the argument is a form id or a form name.
			// Note form name usage is deprecated by supported
			// here for legacy reasons.
			oForm = (document.getElementById(formId) || document.forms[formId]);
		}
		else if(typeof formId == 'object'){
			// Treat argument as an HTML form object.
			oForm = formId;
		}
		else {
			throw "formId should be a form object or form id";
		}
			
		var oElement, oName, oValue, oDisabled, formVars = {};
		var aspNetRegex = new RegExp("[^$]+$", "i");
			
		// Iterate over the form elements collection to construct the
		// label-value pairs.
		for (var i=0; i<oForm.elements.length; i++){
			oElement = oForm.elements[i];
			oDisabled = oForm.elements[i].disabled;
			
			
			oName = oForm.elements[i].name;
			if(isAspNet && aspNetRegex.test(oForm.elements[i].name))
			{
			    oName = aspNetRegex.exec(oForm.elements[i].name)[0];
			}
			
			oValue = oForm.elements[i].value;
	
			// Do not submit fields that are disabled or
			// do not have a name attribute value.
			if(!oDisabled && oName)
			{
				switch (oElement.type)
				{
					case 'select-one':
					case 'select-multiple':
						formVars[oName] = oValue;
						break;
					case 'radio':
					case 'checkbox':
						if(oElement.checked)
						{
							formVars[oName] = oValue;
						}
						break;
					case 'file':
						// stub case as XMLHttpRequest will only send the file path as a string.
					case undefined:
						// stub case for fieldset element which returns undefined.
					case 'reset':
						// stub case for input type reset button.
					case 'button':
						// stub case for input type button elements.
						break;
					case 'submit':
						// stub case for input type submit elements.
						break;
					default:
						formVars[oName] = oValue;
						break;
				}
			}
		}
		
		return formVars;
	}
}
})();

