///<reference path="../TESCO.js" />
///<reference path="event.js" />
///<reference path="event.manager.js" />
///<reference path="DOM.node.js" />
///<reference path="exception.js" />

TESCO.system.Validation = new function() {
    TESCO.system.event.manager.call(this,
		"beforevalidate",
		"validate",
		"beforerendermessage",
		"rendermessage",
		"displaycustommessages",
		"beforemessagealert",
		"messagealert",
		"beforevalidationcomplete",
		"validationcomplete"
	);

    this.VERSION = "1.1.0";
    this.NAME = "TESCO.system.Validation";

    var _self = this;

    var _isValidating = false;
    var _forms = [];
    var _activeElementName = null;
    var _defaultElementNameDefined = false;

    this.form = function(name) {
        this.name = name;
        this.formItems = [];
        this.validators = [];
    }

    this.formItem = function(name) {
        this.validators = [];
        this.name = name;
    }

    // CUSTOM VALIDATORS

    // Regular expression validator
    this.RegularExpression = function(message, expression, buttons) {
        this.type = "RegularExpression";
        this.formItem = null; // Parent
        this.hasError = false;
        this.message = message;
        this.buttons = buttons;

        this.expression = expression;

        this.validate = function(formItem, form) {
            if (_getFormElementValue(formItem) != "") {
                this.hasError = !this.expression.test(_getFormElementValue(formItem));
            } else {
                this.hasError = false;
            }
            return !this.hasError;
        }
    }

    // Is Required validator
    this.IsRequired = function(message, buttons) {
        this.type = "IsRequired";
        this.formItem = null; // Parent
        this.hasError = false;
        this.message = message;
        this.buttons = buttons;

        this.validate = function(formItem, form) {
            this.hasError = (_getFormElementValue(formItem).trim() == "");
            return !this.hasError;
        }
    }

    // Confirmation validator
    this.Confirm = function(message, confirmName, ignoreCase, buttons) {
        this.type = "Confirm";
        this.formItem = null; // Parent
        this.hasError = false;
        this.message = message;
        this.buttons = buttons;

        this.confirmName = confirmName;
        this.ignoreCase = ignoreCase;

        this.validate = function(formItem, form) {
            var oConfirmValue = form.elements[this.confirmName];
            if (oConfirmValue) {
                if (this.ignoreCase) {
                    this.hasError = (oConfirmValue.value.toLowerCase() != _getFormElementValue(formItem).toLowerCase());
                } else {
                    this.hasError = (oConfirmValue.value != _getFormElementValue(formItem));
                }
            } else {
                this.hasError = false;
            }
            return !this.hasError;
        }
    }

    // Comparison validator
    this.Comparison = function(message, compareName, expression, match, validator, buttons) {
        this.type = "Comparison";
        this.formItem = null; // Parent
        this.hasError = false;
        this.message = message;
        this.buttons = buttons;

        this.compareName = compareName;
        if (expression) {
            this.expression = new RegExp(expression);
        }
        this.match = match;
        this.validator = validator;

        this.validate = function(formItem, form) {
            var oCompareValue = form.elements[this.compareName];
            this.hasError = false;
            if (oCompareValue) {
                var sValue;
                if (!oCompareValue.type) {
                    sValue = _getRadioValue(oCompareValue);
                } else {
                    sValue = oCompareValue.value;
                }

                if ((this.expression && this.expression.test(sValue)) || (this.match == sValue)) {
                    this.hasError = !this.validator.validate(formItem, form);
                    if (this.hasError) {
                        this.message = this.validator.message;
                    }
                }
            }
            return !this.hasError;
        }
    }

    // Number validator
    this.Number = function(message, decimalPlaces, allowZero, allowNegative, buttons) {
        this.type = "Number";
        this.formItem = null; // Parent
        this.hasError = false;
        this.message = message;
        this.buttons = buttons;

        this.decimalPlaces = decimalPlaces;
        this.allowZero = allowZero;
        this.allowNegative = allowNegative;

        this.validate = function(formItem, form) {
            this.hasError = false;

            var val = _getFormElementValue(formItem);

            if (val != "") {
                if (isNaN(val)) {
                    this.hasError = true;
                } else {
                    if ((!this.allowNegative && val < 0) ||
							(!this.allowZero && val == 0)) {
                        this.hasError = true;
                    } else {
                        var decimalIndex = val.indexOf(".");
                        if (decimalIndex != -1 && this.decimalPlaces == 0) {
                            this.hasError = true;
                        } else {
                            var d = val.substr(decimalIndex + 1);
                            if (decimalIndex != -1 && d.length > this.decimalPlaces) {
                                this.hasError = true;
                            }
                        }
                    }
                }
            }

            return !this.hasError;
        }
    }

    // Number range validator
    this.Range = function(message, min, max, buttons) {
        this.type = "Range";
        this.formItem; // Parent
        this.hasError = false;
        this.message = message;
        this.buttons = buttons;

        this.max = max;
        this.min = min;

        this.validate = function(formItem, form) {
            this.hasError = false;

            var val = _getFormElementValue(formItem);

            if (val != "") {
                if (isNaN(val) || val < this.min || val > this.max) {
                    this.hasError = true;
                }
            }

            return !this.hasError;
        }
    }

    function _getFormElementValue(formElement) {
        var _value;
        switch (formElement.type) {
            case "checkbox":
                _value = (formElement.checked) ? formElement.value : "";
                break;
            case "radio":
                _value = _getRadioValue(document.getElementsByName(formElement.name));
                break;
            case "textarea":
                // Ensure that a new line in a textarea is always treated as 2 characters in the js...
                _value = formElement.value.replace(/\r{0,}\n/g, "\r\n");
                break;
            case "select-one":
                var _vAttribute = formElement[formElement.selectedIndex].getAttribute("value");
                _value = _vAttribute ? _vAttribute : "";
                break;
            default:
                _value = formElement.value;
        }
        return _value;
    }

    function _getRadioValue(o) {
        if (o) {
            for (var i = 0; i < o.length; i++) {
                if (o[i].checked) {
                    return o[i].value;
                }
            }
        }
        return "";
    }


    // Add a validator
    this.add = function(name, formItems, validators) {
        var f = _forms[name];
        if (!f) {
            f = _forms[name] = new _self.form(name);
        }

        if (formItems) {
            for (var i = 0; i < formItems.length; i++) {
                _appendFormItem(f, formItems[i]);
            }
        }

        if (validators) {
            for (var i = 0; i < validators.length; i++) {
                validators[i].v.formItemName = validators[i].n;
                _appendValidator(f, validators[i].v);
            }
        }
    }

    function _appendFormItem(form, formItem) {
        var fi = form.formItems[formItem.n];
        if (!fi) {
            fi = form.formItems[formItem.n] = new _self.formItem(formItem.n);
        }
        if (formItem.db) {
            fi.defaultButton = formItem.db;
        }

        if (formItem.v) {
            for (var i = 0; i < formItem.v.length; i++) {
                formItem.v[i].formItem = formItem;
                _appendValidator(fi, formItem.v[i]);
            }
        }
    }

    function _appendValidator(formItem, validator) {
        formItem.validators.push(validator);
    }

    // Attach Form Events
    this.attachForm = function(formId) {
        if (document.getElementById && document.getElementsByName) {
            TESCO.system.event.attach(document.getElementById(formId), "submit", validateForm);
            TESCO.system.event.attach(document.getElementById(formId), "click",
				function(e) {
				    if (e.target && e.target.name && !_defaultElementNameDefined) { // && (e.target.type == "image" || e.target.type == "submit")) {
				        _activeElementName = e.target.name;
				    }
				}
			);
            TESCO.system.event.attach(document.getElementById(formId), "keypress",
				function(e) {
				    if (e.target && e.target.name && e.keyCode == 13 && e.target.tagName.toUpperCase() != "TEXTAREA") { // &&  (e.target.type == "image" || e.target.type == "submit")) {
				        if (e.target.form && _forms[e.target.form.id]) {
				            var fi = _forms[e.target.form.id].formItems[e.target.name];
				            if (fi && fi.defaultButton) {
				                _activeElementName = fi.defaultButton;
				                _defaultElementNameDefined = true;
				            }
				        }
				    }
				}
			);
        }
    }

    function validateForm(e) {
        var oEvent = _self.dispatchEvent("beforevalidate", { form: this }, true);
        if (!oEvent.returnValue) {
            e.stopEvent();
            _self.dispatchEvent("validationcomplete");
            return;
        }

        _isValidating = true;

        _self.dispatchEvent("validate", { form: this });

        var validator, firstError, fi;
        var firstErrorDetails = { control: null, message: "", validator: null };

        var oForm = this;
        var oErrorMsg = document.getElementById("h-error");
        if (oErrorMsg) {
            oErrorMsg.style.display = "none";
        }

        var formItems = _forms[this.id].formItems;

        var errorCount, totalErrorCount = 0, oFormElement, message, validatorMessage, radioElementNames = "", bValidate, iIndex, baseName, formElementName, fi;
        for (var i = 0; i < oForm.elements.length; i++) {
            oFormElement = oForm.elements[i];
            if (oFormElement.name && oFormElement.type != "hidden" && oFormElement.type != "button" && oFormElement.type != "submit") {
                bValidate = true;
                if (oFormElement.type == "radio") {
                    if (radioElementNames.indexOf("[" + oFormElement.name + "]") == -1) {
                        radioElementNames += ("[" + oFormElement.name + "]");
                    } else {
                        bValidate = false;
                    }
                }
                if (bValidate) {
                    fi = null;
                    formElementName = new String(oFormElement.name);
                    //	iIndex = formElementName.indexOf("_");
                    if (iIndex != -1) {
                        baseName = formElementName.substr(0, iIndex);
                        if (baseName) {
                            fi = formItems[baseName];
                        }
                    }
                    errorCount = 0, validatorMessage = "";
                    if (!fi) {
                        fi = formItems[oFormElement.name];
                    }
                    if (fi && fi.validators) {
                        for (var ii = 0; ii < fi.validators.length; ii++) {
                            if (!_activeElementName || !fi.validators[ii].buttons || (("[" + fi.validators[ii].buttons.join("][") + "]").indexOf("[" + _activeElementName + "]") != -1)) {
                                fi.validators[ii].validate(oFormElement, oForm);
                                if (fi.validators[ii].hasError) {
                                    errorCount++;
                                    totalErrorCount++;

                                    if (!validatorMessage) {
                                        validatorMessage = fi.validators[ii].message;
                                    }

                                    if (!firstError) {
                                        firstError = oFormElement;
                                        message = validatorMessage;
                                    }
                                }
                            }
                        }
                    }

                    var formValidators = _forms[this.id].validators, validator;
                    for (var ii = 0; ii < formValidators.length; ii++) {
                        validator = formValidators[ii];
                        if (parseId(oFormElement.name).name == validator.formItemName && parseId(oFormElement.name).id == parseId(_activeElementName).id) {
                            validator.validate(oFormElement, oForm);
                            if (validator.hasError) {
                                errorCount++;
                                totalErrorCount++;

                                if (!validatorMessage) {
                                    validatorMessage = validator.message;
                                }

                                if (!firstError) {
                                    firstError = oFormElement;
                                    message = validatorMessage;
                                }
                            }
                        }
                    }
                    _self._setControlMessage((errorCount > 0), oFormElement, validatorMessage);
                }
            }
        }

        if (!firstErrorDetails.control) {
            var form = this;

            // NOTE: Custom messages are only supported client-side to provide additional information that will not be available with scripting  turned off
            var oCustomMessageHandler = new function() {
                this.form = form;

                var _messages = [];

                this.setMessage = function(control, message) {
                    if (control) {
                        _messages[_messages.length] = { control: control, message: message };
                    } else {
                        throw new Error("TESCO.system.Validation.custommessage.setMessage: control passed to setMessage does not exist");
                    }
                }

                _self.dispatchEvent("displaycustommessages", this);

                if (_messages.length > 0) {
                    firstErrorDetails.control = _messages[0].control;
                    firstErrorDetails.message = _messages[0].message;

                    for (var i = 0; i < _messages.length; i++) {
                        _self._setControlMessage(true, _messages[i].control, _messages[i].message);
                    }
                } else {
                    firstErrorDetails.control = firstError;
                    firstErrorDetails.message = message;
                }
            }
        }

        if (firstErrorDetails.control) {
            if (firstErrorDetails.message) {
                if ("“".length > 1 && String.fromCharCode) {	// Check to see of the browser fully supports UTF-8 in javascript...
                    var msg = firstErrorDetails.message;
                    // If not then replace the “ and ” character to a simple " character...
                    msg = msg.replace(String.fromCharCode(8220), "\"");
                    msg = msg.replace(String.fromCharCode(8221), "\"");

                    firstErrorDetails.message = msg;
                }

                var oEvent = _self.dispatchEvent("beforemessagealert", { form: this, details: firstErrorDetails }, true);
                if (oEvent.returnValue) {
                    _self.dispatchEvent("messagealert", { form: this, details: firstErrorDetails });
                }
            }

            e.stopEvent();
        }

        var oEvent = _self.dispatchEvent("beforevalidationcomplete", { form: this, hasErrors: (totalErrorCount > 0) }, true);

        if (oEvent.returnValue === false) {
            e.stopEvent();
        }

        _activeElementName = null;
        _defaultElementNameDefined = false;
        _isValidating = false;

        _self.dispatchEvent("validationcomplete");
        _self.showFirstMessage();
    }

    this.validateForm = validateForm;

    function parseId(id) {
        if (!id) {
            id = "";
        }
        var o = id.split("-");
        return {
            name: o[0],
            id: (o[1] ? o[1] : null),
            index: (o[2] ? o[2] : null)
        };
    }

    this.clearControlMessage = function(formElement) {
        if (formElement) {
            _self._setControlMessage(false, formElement);
        }
    }

    this._setControlMessage = function(hasError, oFormElement, validatorMessage) {
        var oEvent = _self.dispatchEvent("beforerendermessage", { hasError: hasError, formElement: oFormElement, message: validatorMessage }, true);
        if (!oEvent.returnValue) {
            return;
        }

        var id = oFormElement.name + "-emsg";
        var o = document.getElementById(id);
        if (hasError) {
            if (oFormElement.type == "radio") {
                TESCO.system.DOM.node.addClassName(oFormElement.parentNode.parentNode, "r error");
            } else {
                TESCO.system.DOM.node.addClassName(oFormElement.parentNode, "error");
            }

            if (validatorMessage != "") {
                if (!o) {
                    o = TESCO.system.DOM.node.create("span");
                    o.id = id;
                    o.className = "errorMsg";
                    oFormElement.parentNode.parentNode.insertBefore(o, oFormElement.parentNode);
                }
                if (o.style) {
                    o.style.display = "block";
                }

                TESCO.system.DOM.node.setTextValue(o, validatorMessage);
            }
        } else {
            if (oFormElement.type == "radio") {
                TESCO.system.DOM.node.addClassName(oFormElement.parentNode, "r");
            } else {
                TESCO.system.DOM.node.removeClassName(oFormElement.parentNode, "r");
            }

            if (o) {
                o.style.display = "none";
            }
        }
        _self.dispatchEvent("rendermessage", { hasError: hasError, formElement: oFormElement, message: validatorMessage });
    }

    this.showFirstMessage = function() {
        var _errorList = TESCO.system.DOM.node.getElementsByClassAndTagName(document.body, "errorMsg", "span");
        if (_errorList) {
            var _firstError;
            for (var i = 0; i < _errorList.length; i++) {
                if (_errorList[i].style.display == 'block') {
                    _firstError = _errorList[i];
                    break;
                }
            }
            if (_firstError && _firstError.id) {
                document.location.href = '#' + _firstError.id;
            }
        }
    }
}