
/* -------------------------------------------------------------------------
 * validates errors based on format objects
 *
 * each format object has the following fields:
 *
 *     name: the name to store the error under
 *     required: boolean, whether empty fields are allowed
 *     format: the regular expression used to format the content
 *     values: an array of acceptable values
 */
function Validator() {
	var ERRORS = {};

	// validate the supplied data with the supplied format object
	// returns true if the data is valid, false if it isn't
	this.Validate = function(data, format) {
		var errors = true;

		// check required field is filled
		if(format["required"] && data == "") {
			this.Add(format["name"], REQUIRED);
			return false;
		}

		// check data matches the regexp
		if(format["format"] != null) {
			if(!format["format"].test(data) && data != "") {
				this.Add(format["name"], FORMAT);
				errors = false;
			}
		}

		// check data is one of the listed values
		if(format["values"] != null) {
			var has = false;

			// loop through the acceptable values
			for(var i = 0; i < format["values"].length; i++) {
				// the data is in the required list
				if(data == format["values"][i]) has = true;
			}

			// add the error code if has is false
			if(!has) {
				this.Add(format["name"], VALUE);
				errors = false;
			}
		}

		// call the rule function
		if(format["rule"] != null) {
			if(!format["rule"](data)) {
				this.Add(format["name"], RULE);
				errors = false;
			}
		}

		return errors;
	};

	// adds an error key and code to the ERRORS object
	this.Add = function(key, code) {
		// if the ERRORS hash doesn't have the key
		if(!ERRORS[key]) {
			ERRORS[key] = 0;
		}

		// add the new value to the existing value
		ERRORS[key] += code;
	};

	// clear an error by its key
	this.Clear = function(key) {
		ERRORS[key] = null;
	};

	// clears all errors
	this.ClearAll = function() {
		ERRORS = null;
		ERRORS = {};
	};

	/* -----------------------------------------------------------------------------
	 * returns true if the ERRORS object has any data, false otherwise
	 *
	 * There are four "overloads" to this function
	 *
	 * 1: 0 arguments, returns true if there are any errors
	 * 2: (int), returns true if there are any errors of with the supplied code
	 * 3: (string), returns the error code from the supplied error name
	 * 4: (string, int), returns true if the supplied error name has the supplied error code
	 */
	this.HasError = function() {
		if(arguments.length == 0) {
			var count = 0;

			for(var i in ERRORS) {
				count++;
			}

			return (count > 0);
		}
		
		// if argument 0 is a number
		if(/^\d+$/.test(arguments[0])) {
			// returns true if any key in the ERRORS object has the supplied code
			var result = false;

			for(var i in ERRORS) {
				result = this.HasError(i, arguments[0]);

				if(result) return true;
			}

			return false;
		}
		else {
			// returns the error value if the ERRORS object has the supplied key or 0
			var key = arguments[0];

			if(arguments.length == 1) return (ERRORS[key] != null) ? ERRORS[key] : 0;

			return (ERRORS[key] != null && (this.HasError(key) & arguments[1]) > 0);
		}
	};

	// creates a format object and returns it
	this.Format = function(name, required, format, values, rule) {
		var o = {};

		o["name"]     = name;
		o["required"] = required;
		o["format"]   = format;
		o["values"]   = values;
		o["rule"]     = rule;

		o.toString = function() {
			return "name: " + o.name + "\n"
				+ "required: " + o.required + "\n"
				+ "format: " + o.format + "\n"
				+ "values: " + o.values + "\n"
				+ "rule: " + o.rule + "\n";
		}

		return o;
	};
}


// regular expression formats
var Formats = {};

Formats["ADDRESS"]     = /^[a-zåäöüéèáàêëûãç\s\-\'\.,\d]*$/i;
Formats["EMAIL"]       = /^((\"[^\"\f\n\r\t\v\b]+\")|([\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+(\.[\w\!\#\$\%\&\'\*\+\-\~\/\^\`\|\{\}]+)*))@((\[(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))\])|(((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9]))\.((25[0-5])|(2[0-4][0-9])|([0-1]?[0-9]?[0-9])))|((([A-Za-z0-9\-])+\.)+[A-Za-z\-]+))$/i;
Formats["NAME"]        = /^[a-zåäöüéèáàêëûãç\s\-\'\.]{2,}\d*$/i;
Formats["NUMBER"]      = /^\d+$/;
Formats["PHONE"]       = /^(\(?\+?[\d]*\)?)?[\d_\-\s\(\)]+$/;
Formats["POSTCODE"]    = /^[a-z\d\-\s]+$/i;
Formats["POSTCODE_UK"] = /^[A-Z]{1,2}[1-9][0-9]?[A-Z]? [0-9][A-Z]{2,}|GIR 0AA$/i;
Formats["CURRENCY"]    = /^\d+(\.\d{2})?$/;
Formats["PASSWORD"]    = /^[\w]{6,20}$/i;
Formats["DATE"]        = /^((\d{1,2}[\/|\-|\s\.]){2}(\d{2}|\d{4}))$|^(\d{1,2}([a-z]{2})?\s+[a-z]{3,9}\s?(\d{2}|\d{4}))$/i;

// errorcode constants
var REQUIRED = 1;
var FORMAT   = 2;
var VALUE    = 4;
var RULE     = 8;


// error code and text equivalents
var ErrorCode = {};

ErrorCode[REQUIRED] = "A required field has been left blank.";
ErrorCode[FORMAT]   = "A value has been submitted in an incorrect format.";
ErrorCode[VALUE]    = "An unacceptable value has been submitted.";
ErrorCode[RULE]     = "A value violates a rule.";


/* -------------------------------------------------------------------------
 * example usage:
 *
 * var f = validator.Format("email", true, Formats["EMAIL"], null);
 *
 * var result = validator.Validate("email@website.com", f);
 *
 * var bool
 *
 * returns true if validator has any errors
 * bool = validator.HasError();
 *
 * returns true if validator has any code 4 errors
 * bool = validator.HasError(4);
 *
 * returns true if validator has a "name" key with a code 4 error
 * bool = validator.HasError("name", 4);
 */
