/** 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.0.3.0
 */

if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) {
  i || (i = 0);
  var length = this.length;
  if (i < 0) i = length + i;
  for (; i < length; i++)
    if (this[i] === item) return i;
  return -1;
};


(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;
}

function sortErrors(a, b)
{
	var aPos = this.config.fieldOrder.indexOf(a.field);
	var bPos = this.config.fieldOrder.indexOf(b.field);
	
	return (aPos > bPos) ? 1 : -1;
}



FormCheck = function(config)
{
	
	this.config = config;
	that = this;
	
	
}

FormCheck.prototype = {
	formVars : null,
	errors : {},
	/** 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
		};
	},
	/** 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 indexed array with errors, should be used after check() method
	 *  @returns string[]
	 */
	getErrorTexts : function()
	{
		var errorTexts = [];
		for(var field in this.errors)
		{
			for(var errorType in this.errors[field])
			{
				if(!this.config.errorTexts.hasOwnProperty(field))
				{
					throw "Please specify error text for field " + field;
				}
				
				if(!this.config.errorTexts[field].hasOwnProperty(errorType))
				{
					throw "Please specify error text for field " + field + " : " + errorType;
				}
				
				errorTexts[errorTexts.length] = this.config.errorTexts[field][errorType];
				break;
			}
		}
		return errorTexts;
	},
	/** Returns associative array with errors in following format
     *  ARRAY[FIELD_KEY] = FIELD_ERROR
     *  @return array
     */
	getErrorTextsAssoc : function()
	{
		var returnErrors = [];
		
		var fieldOrder = this.config.fieldOrder;
		var errors = this.errors, field = "", errorType = "";
		var errorTexts = this.config.errorTexts;
		for(var i = 0; i < fieldOrder.length; i++)
		{
			field = fieldOrder[i].field;
			if(!errors[field])
			{
			    continue;
			}
			for(var j = 0; j < fieldOrder[i].typeOrder.length; j++)
			{
				errorType = fieldOrder[i].typeOrder[j];
				if(!errors[field][errorType])
				{
					continue;
				}
				
				var message = "";
                if(!errorTexts.hasOwnProperty(field) || !errorTexts[field].hasOwnProperty(errorType))
                {
                    message = "Error text for field " + field + "(" + errorType + ")";
                }
                else
                {
                    message = errorTexts[field][errorType];
                }
				
				returnErrors.push({
                    field : field,
                    message : message
                });
				
				break;
			}
		}
		
		
		
		return returnErrors;
	},
	/** 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;
	},
	/** 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);	
	},
	/** Checks value with e-mail regexp pattern
	 *  @param string value
	 *  @return bool
	 */
	isEmail : function(value)
	{
		var emailPattern = "/^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i";
		return this.matchesRegExp(value, {pattern: emailPattern});
	},
	
	/** 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;
		}
	},
	/** @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);
	},
	/** 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])  );
	},
	/** 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);
	},
	/** 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);
	},
	/** 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;
	}
}
})();

