// prototypal way to handle inheritance
if (typeof Object.create !== "function") {
    Object.create = function (o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

Function.prototype.method = function (name, func) {
    if (!this.prototype[name])
        this.prototype[name] = func;

    return this;
};

Number.method("toInt", function () {
    return Math[(this < 0) ? "ceil" : "floor"](this);
});

String.method("trimRight", function () {
    return this.replace(/\s+$/, "");
});
String.method("trimLeft", function () {
    return this.replace(/^\s+/, "");
});
String.method("trim", function () {
    return this.replace(/^\s+|\s+$/g, "");
});

Object.method("uber", function (name) {
    var that = this, method = that[name];
    return function () {
        return method.apply(that, arguments);
    };
});

// "constant" definitions
//var REGEX_EMAIL = /^([a-zA-Z0-9_'\+\*\$%\^&!\.\-])+@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/; // may need tweaking if parent domain length goes above 4
var REGEX_EMAIL = /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i;
var REGEX_PHONE = /^\+?[0-9\-\.\(\)\s]{7,}$/; // pretty simple international pattern, at this stage
var REGEX_ZIPCODE = /^[0-9]{5}(\-[0-9]{1,5})*$/; // supports US zip codes

var VALIDATOR_SUMMARY_DISPLAY_TYPE = { LIST:0, ORDEREDLIST:1, BLOCK:2, INLINE:3, ALERT:4 }; // pseudo-enumeration
var VALIDATOR_DISPLAY_TYPE = { NONE:0, NUMERIC:1, CHAR:2 }; // pseudo-enumeration

// a "base" class for Form Field Validators
var fieldValidator = function (spec) {
    spec.ctlValidate = (typeof(spec.ctlValidate) === "object") ? spec.ctlValidate : $("#" + spec.ctlValidate);
    spec.ctlError = (typeof(spec.ctlError) === "object") ? spec.ctlError : $("#" + spec.ctlError);
    spec.shortMessage = spec.shortMessage || "*";
    spec.message = spec.message;
	spec.isDynamic = (typeof(spec.isDynamic) === "boolean") ? spec.isDynamic : false;
    spec.grabFocus = (typeof(spec.grabFocus) === "boolean") ? spec.grabFocus : false;

    var that = {};

    spec.isValid = true; // assume is valid, before being validated

    that.get_short_message = function () {
        return spec.shortMessage;
    };

    that.get_message = function () {
        return spec.message;
    };

    that.is_valid = function () {
        return spec.isValid;
    };

    that.display = function () {
        if (spec.ctlError !== null) {
            if (spec.isValid) {
                spec.ctlValidate.parent().removeClass("frmCtlError");
                //spec.ctlValidate.removeAttr("title");
                spec.ctlError.html("");
            } else {
                spec.ctlValidate.parent().removeClass("frmCtlError");
                spec.ctlValidate.parent().addClass("frmCtlError");
                //spec.ctlError.attr("title", spec.message);
                spec.ctlError.html(spec.shortMessage);
            }
        }
    };

    // private function to add the validation to the control's blur event
    var setDynamic = function () {
		if (spec.ctlValidate !== null) {
			spec.ctlValidate.blur(function () {
				that.validate();
                that.display();
			});
		}
	};

    if (spec.isDynamic)
        spec.isDynamic = setDynamic();

    return that;

};

var requiredField = function (spec) {
    var that = fieldValidator(spec);

    that.validate = function () {
        spec.isValid = (spec.ctlValidate.val() && spec.ctlValidate.val().length > 0) ? true : false;
        that.display();
        return spec.isValid;
    };

    return that;
};

var regexField = function (spec) {
    spec.regex  = (typeof(spec.regex) === "string") ? new RegExp(spec.regex) : spec.regex;
    spec.canBeEmpty  = (typeof(spec.canBeEmpty) === "boolean") ? spec.canBeEmpty : false;

    var that = fieldValidator(spec);

    that.validate = function () {
	    spec.isValid = ((spec.canBeEmpty && spec.ctlValidate.val() == "") || (spec.ctlValidate.val() && spec.ctlValidate.val().match(spec.regex))) ? true : false;
        that.display();
        return spec.isValid;
    };

    return that;
};

var validator = function (spec) {
    spec.displayType = spec.displayType || VALIDATOR_DISPLAY_TYPE.NONE;
    spec.summaryType = spec.summaryType || VALIDATOR_SUMMARY_DISPLAY_TYPE.LIST;
    spec.summaryElement = spec.summaryElement || $("#vgSummary");
    spec.summaryElement = (spec.summaryElement === "object") ? spec.summaryElement : $("#" + spec.summaryElement);
    spec.showSummary = (typeof(spec.showSummary) === "boolean") ? spec.showSummary : false;

    var that = {};
    var validators = [];
    var isValid = true;

    that.validators = function () {
        return validators;
    };

    that.add_validator = function (v) {
        if (v) {
            if (v instanceof Array)
                validators = validators.concat(v);
            else
                validators.push(v);
        }
    };

    that.is_valid = function () {
        return isValid;
    };

	that.validate = function() {
        isValid = true;
		if (validators.length > 0) {
			var iFailed = 1;

			for (var i = 0; i < validators.length; i++) {
				if (validators[i].validate() === false) {
					if (spec.displayType === VALIDATOR_DISPLAY_TYPE.NUMERIC) {
						validators[i].get_short_message() = iFailed++;
						validators[i].display();
					}
					isValid = false;
				}
			}

            show();
		}

		return isValid;
	};

	var show = function() {
		if (spec.showSummary && (spec.summaryElement !== null) && (validators.length > 0)) {
			var output = "";

            if (spec.summaryType === VALIDATOR_SUMMARY_DISPLAY_TYPE.ALERT) {
                for (var i = 0; i < validators.length; i++) {
                    if (validators[i].is_valid() === false)
                        output += validators[i].get_message() + "\n";
                }
                alert(output);
            } else {
                switch (spec.summaryType) {
                    case VALIDATOR_SUMMARY_DISPLAY_TYPE.LIST:
                        output += "<ul>";
                        for (var i = 0; i < validators.length; i++) {
                            if (validators[i].is_valid() === false)
                                output += "<li>" + validators[i].get_message() + "</li>";
                        }
                        output += "</ul>";
                        break;
                    case VALIDATOR_SUMMARY_DISPLAY_TYPE.ORDEREDLIST:
                        output += "<ol>";
                        for (var i = 0; i < validators.length; i++) {
                            if (validators[i].is_valid() === false)
                                output += "<li>" + validators[i].get_message() + "</li>";
                        }
                        output += "</ol>";
                        break;
                    case VALIDATOR_SUMMARY_DISPLAY_TYPE.BLOCK:
                        for (var i = 0; i < validators.length; i++) {
                            if (validators[i].is_valid() === false)
                                output += "<span style=\"shortMessage:block;\">" + validators[i].get_message() + "</span>";
                        }
                        break;
                    case VALIDATOR_SUMMARY_DISPLAY_TYPE.INLINE:
                        for (var i = 0; i < validators.length; i++) {
                            if (validators[i].is_valid() === false)
                                output += "<span>" + validators[i].get_message() + "</span>";
                        }
                        break;
                }

                spec.summaryElement.html(output);
            }
		}
	};

    return that;
};