function ValidationEngine()
{
	var args = arguments;
	this._operators = [];
	this._rules = [];
	this._ruleLoader = null;
	this._fieldSource = null;
	this._mandatoryErrors = {};
	this._validationErrors = {};

	for (var i =
	[
		"<", "CLessThanOperator", 
		"<=", "CLessThanEqualOperator", 
		">", "CGreaterThanOperator", 
		">=", "CGreaterThanEqualOperator", 
		"+", "CAddOperator", 
		"-", "CSubtractOperator", 
		"==", "CEqualityOperator", 
		"!=", "CInequalityOperator", 
		"*", "CMultiplicationOperator", 
		"/", "CDivisionOperator", 
		"%", "CModulusOperator", 
		"||", "CLogicalOrOperator", 
		"&&", "CLogicalAndOperator", 
		"xor", "CLogicalXorOperator", 
		"!", "CLogicalNotOperator", 
		"lt", "CStringLessThanOperator",
		"le", "CStringLessThanEqualOperator",
		"gt", "CStringGreaterThanOperator",
		"ge", "CStringGreaterThanEqualOperator",
		"eq", "CStringEqualityOperator",
		".", "CConcatenationOperator", 
		"=~", "CRegExpOperator",
		"dlt", "CDateLessThanOperator",
		"dle", "CDateLessThanEqualOperator",
		"dgt", "CDateGreaterThanOperator",
		"dge", "CDateGreaterThanEqualOperator",
		"deq", "CDateEqualityOperator",
		"date", "CDateDateOperator",
		"time", "CDateTimeOperator"
	].iterator(); !i.eol(); this.registerOperator(i.next(), i.next()));
	
}

ValidationEngine.VE_MANDATORY_ERROR = 1;
ValidationEngine.VE_VALIDATION_ERROR = 2;

ValidationEngine.prototype =
{
	registerOperator : function(mnemonic, className)
	{
		this._operators[mnemonic] = className;
		return(this);
	},
	registerFieldSource : function(fieldSource)
	{
		this._fieldSource = fieldSource;
		return(this);
	},
	addRule : function(rule)
	{
		this._rules.push(rule);
		return(this);
	},
	registerRuleLoader : function(ruleLoader)
	{
		this._ruleLoader = ruleLoader;
		return(this);
	},
	loadRules : function()
	{
		while (this._ruleLoader.hasMoreRules())
		{
			this.addRule(this._ruleLoader.getRule());
		}
		return(this);
	},
	_addError : function(rule, errorType)
	{
		var VE_DEFAULT_MANDATORY_MESSAGE = "Please complete the field";
		var VE_DEFAULT_VALIDATION_MESSAGE = "This field is incorrect";

		var dest = (errorType == 1) ? this._mandatoryErrors : this._validationErrors;
		var message = (errorType == 1) ? rule.caption || VE_DEFAULT_MANDATORY_MESSAGE : rule.hint || VE_DEFAULT_VALIDATION_MESSAGE;

		var ve = this;
		message = message.replace(/\$\{([^\}]+)\}/g, function(s, p1) { return(ve._fieldSource.getValue(p1)); });

		if (!dest[rule.fieldName]) dest[rule.fieldName] = { caption : rule.caption.replace(/\$\{([^\}]+)\}/, function(s, p1) { return(ve._fieldSource.getValue(p1)); }), messages : [] };
		dest[rule.fieldName].messages.push(message);		
		return(this);
	},	
	valid : function()
	{
		return(!(this._mandatoryErrors.length && this._validationError.length));
	},
	validate : function()
	{
		var valid = true;
		this._mandatoryErrors = [];
		this._validationErrors = [];
		
		for (i = 0; rule = this._rules[i++];)
		{
			if (this._isEmpty(this._fieldSource.getValue(rule.fieldName),false))
			{
				this._isMandatory(rule) && (this._addError(rule, 1), valid = false);
			}
			else if (!this._evaluate(rule, rule.pattern))
			{
				this._addError(rule, 2), valid = false;
			}
		}
		return(valid);
	},
	_evaluate : function(rule, pattern)
	{
		var tok;
		var tok2;
		var id=this.ID;
		var stack=[];
		tok=pattern.split(/::/);
		for (var i = 0; i < tok.length; i++)
		{
			op = tok[i];
			if (this._operators[op])
			{
				var oOperator = (typeof this._operators[op] == "object") ? this._operators[op] : this._operators[op] = eval("new " + this._operators[op] + "()");
				var callback = "oOperator.invoke("
				for (var j=0; j<oOperator.cardinality; callback += "stack.pop(),", j++) {};
				stack.push(eval(callback.replace(/,$/,"") + ")"));
			}
			else if(op.search(/##/)!=-1)
			{
				tok2=op.split(/##/);
				stack.push(this._matchName(tok2[0].replace(/^\$?\{?([^\}]+)\}?$/,"$1"),tok2[1]));
			}
			else if(op.search(/^!?\/.+\/[irsm]*$/)!=-1){stack.push(this._matchName(rule.fieldName,op));}
			else if(op.match(/^\$\{([^\}]+)\}$/)){stack.push(this._fieldSource.getValue(RegExp.$1));}
			else {stack.push(op);}
		}
		return(stack.pop());
	},
	_isMandatory : function(rule)
	{
		return((rule.fieldName==rule.depend||rule.depend==".")?true:(rule.depend=="_"?false:this._evaluate(rule, rule.depend)));
	},
	_isEmpty : function(fieldValue, allowSpaces)
	{
		return(allowSpaces?fieldValue.match(/^[ ]*$/)!=null:fieldValue.match(/^$/)!=null);
	},
	getErrorsForField : function(fieldName)
	{
		return((this._mandatoryErrors[fieldName] || this._validationErrors[fieldName] || []).join(""));
	},
	getFieldsForRules : function()
	{
		var fieldNames = [];
		for (var i = this._rules.iterator(); !i.eol(); fieldNames.push(i.next().fieldName));
		return(fieldNames)
	},
	_matchName : function(fieldName, pattern)
	{
		return(this._matchValue(this._fieldSource.getValue(fieldName),pattern));
	},
	_matchValue : function(value, pattern)
	{
		var negate=false;
		(pattern=pattern.replace(/^(!)?(\/.+\/)([^\/]*)$/,"$2"))&&(negate=(RegExp.$1=="!"));
		var mod=RegExp.$3;
		if(mod)
		{
			(mod.search(/i/)!=-1)&&(value=value.toLowerCase());
			(mod.search(/r/)!=-1)&&(value=value.split("").reverse().join(""));
			(mod.search(/m/)!=-1)&&(value=value.replace(/[\x0a\x0d]/g,""));
		}
		var valid=(eval("value.search("+pattern+")")!=-1);
		return(negate?!valid:valid);
	},
	getMandatoryErrors : function()
	{
		return(this._mandatoryErrors.clone());
	},
	getValidationErrors : function()
	{
		return(this._validationErrors.clone());
	},
	toString : function()
	{
		return("toString method to be implemented");
	}
};

function Rule(hash)
{
	this.fieldName = hash.fieldName
	this.caption = hash.caption
	this.depend = hash.depend
	this.pattern = hash.pattern
	this.hint = hash.hint
}
Rule.prototype =
{
	toString : function() { return("toString method to be implemented"); }
};

function AbstractRuleLoader() { }
AbstractRuleLoader.prototype =
{
	init : function() {},
	up : function() { alert("up must be sublcassed"); },
	down : function() { alert("down must be sublcassed"); },
	hasMoreRules : function() { alert("hasMoreRules must be sublcassed"); },
	getRule : function() { alert("getRule must be sublcassed"); }
};


