function isNull(value)
{
	return !(/\S+/.test(value));
}

function isInteger(value)
{
	return /^\d+$/.test(value);
}

function isFloat(value, minDecimal, maxDecimal, decimalsSeparator, thousandsSeparator)
{
	var 
		minDecimalValue = (typeof(minDecimal) != "undefined") ? minDecimal : null,
		maxDecimalValue = (typeof(maxDecimal) != "undefined") ? maxDecimal : null,
		decimalsSeparatorValue = (typeof(decimalsSeparator) != "undefined") ? decimalsSeparator : ",",
		thousandsSeparatorValue = (typeof(thousandsSeparator) != "undefined") ? thousandsSeparator : null,
		r = new RegExp("^" + ((thousandsSeparatorValue == null) ? "(\\d)*" : "(\\d{1,3})?(\\" + thousandsSeparatorValue + "\\d{3})*") + "(\\" + decimalsSeparatorValue + "{1}\\d" + ((minDecimalValue == null) ? "*" : "{" + (minDecimalValue + ((maxDecimalValue == null) ? "" : "," + maxDecimalValue) + "}")) + ")" + ((minDecimalValue > 0) ? "{1}": "?") + "$");

	return isNull(value) ? false : r.test(value);
}

function isMoney(value, minDecimal, maxDecimal, decimalsSeparator, thousandsSeparator)
{
	var 
		minDecimalValue = (typeof(minDecimal) != "undefined") ? minDecimal : "0",
		maxDecimalValue = (typeof(maxDecimal) != "undefined") ? maxDecimal : "4",
		decimalsSeparatorValue = (typeof(decimalsSeparator) != "undefined") ? decimalsSeparator : ",",
		thousandsSeparatorValue = (typeof(thousandsSeparator) != "undefined") ? thousandsSeparator : ".";
		
	return isFloat(value, minDecimalValue, maxDecimalValue, decimalsSeparatorValue, thousandsSeparatorValue);
}

function isCNPJ(cnpj) 
{
	var i, c1, c2;
	var dv1, dv2;
	var sum, factor;

	c1 = parseInt(cnpj.substring(12, 13), 10);
	c2 = parseInt(cnpj.substring(13, 14), 10);

	sum = 0;
	factor = 0;

	for (i = 1; i <= 12; i++)
	{
		factor += (i < 9) ? 1 : -7;
		sum = sum + (factor * parseInt(cnpj.substring(12 - i, 12 - i + 1), 10))
	}

	dv1 = (sum % 11); 
	dv1 = 11 - dv1;

	if(dv1 > 9) dv1 = 0;
	if(c1 != dv1) return false;

	sum = 0;
	factor = 0;

	for (i = 1; i <= 13; i++)
	{
		factor += (i < 9) ? 1 : -7;
		sum = sum + (factor * parseInt(cnpj.substring(13 - i, 13 - i + 1), 10))
	}

	dv2 = (sum % 11);
	dv2 = 11 - dv2;
  
	if(dv2 > 9) dv2 = 0;
	if(c2 != dv2) return false;

	return true;
}

function evaluateProperty(field, property)
{
	return eval((property == null) ? field.fieldName : field.fieldName + "." + property);
}

function evaluateConditionProperty(condition)
{
	return evaluateProperty(condition.field, condition.property);
}

function focusElement(elementName, usingOnNetscape4)
{	
	if (elementName != null) 
	{
		eval(usingOnNetscape4 ? elementName + ".focus()" : "try { " + elementName + ".focus(); } catch(e) {}");
	}
}

function IsNotNullCondition(property)
{
	this.field = null;
	this.property = property;
	
	this.validate = function()
	{
		return !isNull(evaluateConditionProperty(this));
	}
}

function IsTextCondition(property, minLength, maxLength)
{
	this.field = null;
	this.property = property;
	this.minLength = (arguments.length > 1) ? minLength : null;
	this.maxLength = (arguments.length > 2) ? maxLength : null;
	
	this.validate = function()
	{
		var
			value = evaluateConditionProperty(this);
			
		if (!isNull(value))
		{
			if ((this.minLength != null) && (this.maxLength != null))
			{
				return ((value.length >= this.minLength) && (value.length <= this.maxLength));
			}
			else if (this.minLength != null)
			{
				return (value.length >= this.minLength);
			}
			else if (this.maxLength != null)
			{
				return (value.length <= this.maxLength);
			}
			else
			{
				return true;
			}
		}
		else
		{
			return false;
		}
	}
}

function LengthCondition(property, minLength, maxLength)
{
	this.field = null;
	this.property = property;
	this.minLength = (arguments.length > 1) ? minLength : null;
	this.maxLength = (arguments.length > 2) ? maxLength : null;
	
	this.validate = function()
	{
		var 
			l = evaluateConditionProperty(this).length;
		
		if ((this.minLength != null) && (this.maxLength != null))
		{
			return ((l >= this.minLength) && (l <= this.maxLength));
		}
		else if (this.minLength != null)
		{
			return (l >= this.minLength);
		}
		else if (this.maxLength != null)
		{
			return (l <= this.maxLength);
		}
		else
		{
			return true;
		}
	}
}

function IsIntegerCondition(property, minValue, maxValue)
{
	this.field = null;
	this.property = property;
	this.minValue = (arguments.length > 1) ? minValue : null;
	this.maxValue = (arguments.length > 2) ? maxValue : null;
	
	this.validate = function()
	{
		if (isInteger(evaluateConditionProperty(this)))
		{
			var
				value = parseInt(evaluateConditionProperty(this), 10);
		
			if ((this.minValue != null) && (this.maxValue != null))
			{
				return ((value >= this.minValue) && (value <= this.maxValue));
			}
			else if (this.minValue != null)
			{
				return (value >= this.minValue);
			}
			else if (this.maxValue != null)
			{
				return (value <= this.maxValue);
			}
			else
			{
				return true;
			}
		}
		else
		{
			return false;
		}
	}
}

function IsFloatCondition(property, minDecimalValue, maxDecimalValue, decimalsSeparator, thousandsSeparator)
{
	this.field = null;
	this.property = property;
	this.minDecimalValue = minDecimalValue;
	this.maxDecimalValue = maxDecimalValue;
	this.decimalsSeparator = decimalsSeparator;
	this.thousandsSeparator = thousandsSeparator;
	
	this.validate = function()
	{
		return isFloat(evaluateConditionProperty(this), this.minDecimalValue, this.maxDecimalValue, this.decimalsSeparator, this.thousandsSeparator);
	}
}

function IsMoneyCondition(property, minDecimalValue, maxDecimalValue, decimalsSeparator, thousandsSeparator)
{
	this.field = null;
	this.property = property;
	this.minDecimalValue = minDecimalValue;
	this.maxDecimalValue = maxDecimalValue;
	this.decimalsSeparator = decimalsSeparator;
	this.thousandsSeparator = thousandsSeparator;
	
	this.validate = function()
	{
		return isMoney(evaluateConditionProperty(this), this.minDecimalValue, this.maxDecimalValue, this.decimalsSeparator, this.thousandsSeparator);
	}
}

function IsTimeCondition(property)
{
	this.field = null;
	this.property = property;
	
	this.validate = function()
	{
		return dateFunctions.isTime(evaluateConditionProperty(this));
	}
}

function IsDateCondition(property)
{
	this.field = null;
	this.property = property;
	
	this.validate = function()
	{
		return dateFunctions.isDate(evaluateConditionProperty(this));
	}
}

function IsDateTimeCondition(property)
{
	this.field = null;
	this.property = property;
	
	this.validate = function()
	{
		return dateFunctions.isDateTime(evaluateConditionProperty(this));
	}
}

/*
 Para que esta função seja executada é necessário a lib "cpf_cnpj.js"
*/
function IsCpfCondition(property)
{
	this.field = null;
	this.property = property;
	
	this.validate = function()
	{
		return verificarCpfCnpj(evaluateConditionProperty(this), false);
	}
}

function FunctionCondition(property, conditionFunction)
{
	this.field = null;
	this.property = property;
	this.conditionFunction = conditionFunction;
	
	this.validate = function()
	{
		return this.conditionFunction(evaluateConditionProperty(this));
	}
}

function ExpressionCondition(argument1, argument2)
{
	this.field = null;
	
	if (arguments.length > 1)
	{
		this.property = argument1;
		this.expression = argument2;
	}
	else
	{
		this.property = null;	
		this.expression = argument1;	
	}
	
	this.validate = function()
	{
		var	t;
		if (this.property == null)
		{			
			t = this.field.fieldName;
		}
		else
		{
			t = this.field.fieldName + "." + this.property;							
		}

		return eval(this.expression.replace(/\{t\}/g, t));
	}
	/*this.validate = function()
	{
		return eval(this.expression.replace(/\{t\}/g, evaluateConditionProperty(this)));
	}*/
}

function IsEmailCondition(property)
{
	this.field = null;
	this.property = property;
	
	this.validate = function()
	{
		return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(evaluateConditionProperty(this));
	}
}

function IsUrlCondition(property)
{
	this.field = null;
	this.property = property;
	
	this.validate = function()
	{
		var
			p = eval(this.field.fieldName + "." + this.property);		
		return p.search(/http:/) != -1;
	}
}

function HasElementsCondition(minElements, maxElements)
{
	this.field = null;
	this.minElements = (arguments.length > 0) ? minElements : null;
	this.maxElements = (arguments.length > 1) ? maxElements : null;
	
	this.validate = function()
	{
		var
			length = evaluateProperty(this.field, "options.length");
			
		if ((this.minElements != null) && (this.maxElements != null))
		{
			return ((length >= this.minElements) && (length <= this.maxElements));
		}
		else if (this.minElements != null)
		{
			return (length >= this.minElements);
		}
		else if (this.maxElements != null)
		{
			return (length <= this.maxElements);
		}
		else
		{
			return length > 0;
		}
	}
}

function IsSelectedCondition(ignoreFirstOption, minElements, maxElements)
{
	this.field = null;
	this.ignoreFirstOption = (arguments.length > 0) ? ignoreFirstOption : null;
	this.minElements = (arguments.length > 1) ? minElements : null;
	this.maxElements = (arguments.length > 2) ? maxElements : null;
	
	this.validate = function()
	{
		var
			field = evaluateProperty(this.field);
			
		if (field.multiple)
		{ 
			var
				ops = field.options;
				
			if ((this.minElements != null) && (this.maxElements != null))
			{
				return ((ops.length >= this.minElements) && (ops.length <= this.maxElements));
			}
			else if (this.minElements != null)
			{
				return (ops.length >= this.minElements);
			}
			else if (this.maxElements != null)
			{
				return (ops.length <= this.maxElements);
			}
			else
			{
				return ops.length > 0;
			}
		}
		else
		{
			return field.selectedIndex >= ((this.ignoreFirstOption) ? 1 : 0);
		}
	}
}

function InputField(inputValidator, fieldName, isRequired, requirementCondition)
{
	this.fieldName = fieldName;
	this.isRequired = isRequired;
	this.requirementCondition = requirementCondition;
	this.conditions = [];
	this.action = null;
	this.currentCondition = null;
	this.inputValidator = inputValidator;
	
	if (this.requirementCondition != null)
	{
		this.requirementCondition.field = this;
	}
	
	this.defaultAction = function(message)
	{
		this.inputValidator.handleError(this.currentCondition[1], this.fieldName);
	}
	
	this.addCondition = function(condition, errorMessage)
	{
		this.conditions[this.conditions.length] = new Array(condition, errorMessage);
		condition.field = this;
	}
	
	this.validate = function()
	{
		if (!this.isRequired)
		{
			if (this.requirementCondition != null)
			{
				if (!requirementCondition.validate())
				{
					return true;
				}
			}
		}
		
		var
			result = true;
			
		for (var i = 0; i < this.conditions.length; i++)
		{
			this.currentCondition = this.conditions[i];
			if (!this.currentCondition[0].validate()) 
			{
				if (this.action != null)
				{
					if (typeof(this.action) == "function")
					{
						this.action();
					}
					else if (typeof(this.action) == "string")
					{
						eval(this.action);
					}
				}
				else
				{
					this.defaultAction();
				}
				if (this.inputValidator.stopOnFirstError)
				{
					return false;
				}
				else 
				{
					result = false;
				}
			}
		}
		if (this.inputValidator.stopOnFirstError)
		{
			return true;
		}
		else
		{
			return result;
		}
	}
}

function InputRule(inputValidator, message)
{
	this.rule = null;
	this.action = null;
	this.message = message;
	this.inputValidator = inputValidator;

	this.defaultAction = function(target, message)
	{
		this.inputValidator.handleError(((arguments.length > 1) ? message : this.message), ((arguments.length > 0) ? target : null));
	}
		
	this.validate = function()
	{
		if (this.rule != null) 
		{
			var 
				result = ((typeof(this.rule) == "string") ? eval(this.rule) : this.rule());
				
			if (!result)
			{
				if (this.action != null)
				{
					if (typeof(this.action) == "function")
					{
						this.action();
					}
					else
					{
						eval(this.action);
					}
				}
				else
				{
					this.defaultAction();
				}
				return false;
			}
			return true;
		}
		else
		{
			alert("Rule doesn't have associated function.");
			return true;
		}
	}
}

function MessageTemplate(pattern)
{
	this.pattern = pattern;
	
	this.format = function(values)
	{
		var re = new RegExp();
		
		re.compile("\\%");
		result = this.pattern;
		for (var i = 0; i < values.length; i++)
		{
			result = result.replace(re, values[i]);
		}
		
		return result;
	}
}

function Message(templateName, values)
{
	this.templateName = templateName;
	this.values = values;
}

function InputValidator(formName, stopOnFirstError, usingOnNetscape4)
{
	this.objects = new Array();
	this.messageTemplates = new Array();
	this.formName = formName;
	this.stopOnFirstError = (arguments.length > 1) ? stopOnFirstError : true;
	this.usingOnNetscape4 = (arguments.length > 2) ? usingOnNetscape4 : false;
	this.errors = [];
	this.action = null;	
	
	this.qualifyFieldName = function(fieldName)
	{
		return (this.formName + "." + fieldName);
	}
	
	this.verifyCondition = function(value)
	{
		return (typeof(value) == "undefined") ? null : value;
	}
	
	this.formatMessage = function(message)
	{
		return this.messageTemplates[message.templateName].format(message.values);
	}
	
	this.handleError = function(message, target)
	{
		if (this.action != null)
		{
			if (typeof(this.action) == "function")
			{
				this.action();
			}
			else if (typeof(this.action) == "string")
			{
				eval(this.action);
			}
		}
		
		var
			messageValue = (typeof(message) == "string") ? message : this.formatMessage(message);
		
		if (this.stopOnFirstError)
		{
			alert(messageValue);
			focusElement(target, this.usingOnNetscape4);
		}
		else
		{
			this.errors[this.errors.length] = new Array(messageValue, target);
		}
	}

	this.validate = function()
	{
		for (var i = 0; i < this.objects.length; i++)
		{
			if (typeof(this.objects[i].isRequired) != "undefined")
			{
				if (this.objects[i].isRequired)
				{
					if (this.objects[i].conditions.length == 0)
					{
						alert("Required field \"" + this.objects[i].fieldName + "\" doesn't have any associated condition.");
						return false;
					}
				}
				else
				{
					if (this.objects[i].requirementCondition == null)
					{
						alert("Optional field \"" + this.objects[i].fieldName + "\" doesn't have an associated requirement condition.");
						return false;
					}
				}
			}
		}
		
		var
			result = true;

		this.errors = [];
		for (var i = 0; i < this.objects.length; i++)
		{
			if (!this.objects[i].validate()) 
			{
				if (this.stopOnFirstError)
				{
					return false;
				}
				else
				{
					result = false;
				}
			}
		}
		if (this.stopOnFirstError)
		{
			return true;
		}
		else
		{
			if (this.errors.length != 0)
			{
				var 
					errorMessage = "";
					
				for (var i = 0; i < this.errors.length; i++)
				{
					errorMessage += (this.errors[i][0] + "\n");
				}
				alert(errorMessage);
				focusElement(this.errors[0][1], this.usingOnNetscape4);
			}
			return result;
		}
	}
	
	this.addMessageTemplate = function(templateName, pattern)
	{
		var messageTemplate = new MessageTemplate(pattern);
		
		this.messageTemplates[templateName] = messageTemplate;
		return messageTemplate;
	}
	
	this.addRule = function(message)
	{
		var rule = new InputRule(this, message);
		
		this.objects[this.objects.length] = rule;
		return rule;
	}
	
	this.addRequiredField = function(fieldName)	
	{
		var field = new InputField(this, this.qualifyFieldName(fieldName), true, null);
		
		this.objects[this.objects.length] = field;
		return field;
	}
	
	this.addOptionalField = function(fieldName, condition)
	{
		var field = new InputField(this, this.qualifyFieldName(fieldName), false, condition);

		this.objects[this.objects.length] = field;
		return field;
	}
	
	this.createField = function(fieldName, isRequired, requirementCondition, message)
	{
		var
			field = (isRequired) ? 
				this.addRequiredField(fieldName) : 
				this.addOptionalField(fieldName, ((this.verifyCondition(requirementCondition) != null) ? requirementCondition: new IsNotNullCondition("value")));
				
		return field;
	}
	
	this.addTextField = function(fieldName, isRequired, requirementCondition, message, minLength, maxLength)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsTextCondition("value", minLength, maxLength), message);
		return field;
	}
	
	this.addIntegerField = function(fieldName, isRequired, requirementCondition, message, minValue, maxValue)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsIntegerCondition("value", minValue, maxValue), message);
		return field;
	}

	this.addTimeField = function(fieldName, isRequired, requirementCondition, message)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsTimeCondition("value"), message);
		return field;
	}

	this.addDateField = function(fieldName, isRequired, requirementCondition, message)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsDateCondition("value"), message);
		return field;
	}
	
	this.addDateTimeField = function(fieldName, isRequired, requirementCondition, message)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsDateTimeCondition("value"), message);
		return field;
	}
	
	this.addEmailField = function(fieldName, isRequired, requirementCondition, message)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsEmailCondition("value"), message);
		return field;
	}
	
	this.addFloatField = function(fieldName, isRequired, requirementCondition, message, minDecimal, maxDecimal, decimalsSeparator, thousandsSeparator)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsFloatCondition("value", minDecimal, maxDecimal, decimalsSeparator, thousandsSeparator), message);
		return field;
	}
	
	this.addMoneyField = function(fieldName, isRequired, requirementCondition, message, minDecimal, maxDecimal, decimalsSeparator, thousandsSeparator)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsMoneyCondition("value", minDecimal, maxDecimal, decimalsSeparator, thousandsSeparator), message);
		return field;
	}
	
	this.addListField = function(fieldName, isRequired, requirementCondition, message, ignoreFirstOption, minElements, maxElements)
	{
		var 
			field = this.createField(fieldName, isRequired, requirementCondition, message);

		field.addCondition(new IsSelectedCondition(ignoreFirstOption, minElements, maxElements), message);
		return field;
	}
}