Form = {};

Form.Validator = function() {
}

Form.Validator.prototype.init = function() {
    this.ready = true;
    this.fields = [];
    this.onReadyListener = {};
}

// name, labelElement and validators are optional
Form.Validator.prototype.add = function(name, widget, labelElement, validators, positionName, errorContainer) {
    var me = this;
    var fieldToValidate = {
        name: name,
        widget: widget,
        labelElement: labelElement,
        validators: validators ? validators : null,
        positionName: positionName ? positionName : 'right',
        isReady: (typeof widget.isReady == 'function') ? widget.isReady() : true
    };
    if (errorContainer) {
        fieldToValidate.errorContainer = errorContainer;
    }
    this.fields.push(fieldToValidate);
    
    if (typeof widget.isReady == 'function') {
        this.onReadyListener[name] = function(ready) {
           fieldToValidate.isReady = ready;
           me.onReadyStateChange();
        };
        widget.addListener('readystatechange', this.onReadyListener[name]);
    }
}

Form.Validator.prototype.isReady = function() {
    var newReady = true;
    for (var i = 0; i < this.fields.length; ++i) {
        if (!this.fields[i].isReady) {
            newReady = false;
            break;
        }
    }
    return newReady;
}

Form.Validator.prototype.remove = function(name) {
    for (i = 0; i < this.fields.length; i++) {
        if (this.fields[i].name == name) {
            if (this.onReadyListener[name]) {
                widget.removeListener('readystatechange', this.onReadyListener[name]);
            }
            this.clearErrorsFromField(this.fields[i]);
            this.fields.splice(i, 1);
            this.onReadyStateChange(); // just in case check if the isReady state changed
            break;
        }
    }
}

Form.Validator.prototype.onReadyStateChange = function() {
    var newReady = this.isReady();
    if (newReady != this.ready) {
        this.ready = newReady;
        this.dispatchEvent('readystatechange', this.ready);
    }
}

Form.Validator.prototype.callWhenReady = function(callBack) {
    var me = this;
    if (this.ready) {
        timeoutManager.set(function() { callBack(); }, 0);
    } else {
        this.addListener('readystatechange', function() {
            if (me.isValid(true)) {
                callBack();
            }
        });
    }
}

Form.Validator.prototype.isValid = function(markErrors) {
    var errors = this.getValueErrors(markErrors);
    return errors.length == 0;
}

Form.Validator.prototype.getValueErrorsByField = function(markErrors) {
    var me = this;
    var errorsByField = [];
    
    function handleInvalidField(field, errorMsg) {
        errorsByField.push({field: field, errorMsg: errorMsg});
    }
    
    for (var i = 0; i < this.fields.length; ++i) {
        var valueErrors = [];
        if (this.fields[i].widget.getValueErrors) {
            var valueErrors = this.fields[i].widget.getValueErrors(markErrors);
            for (var j = 0; j < valueErrors.length; ++j) {
                handleInvalidField(this.fields[i], valueErrors[j]);
            }
        }
        if (this.fields[i].validators) {
            for (var j = 0; j < this.fields[i].validators.length; ++j) {
                if (!this.fields[i].validators[j].isValid(this.fields[i].widget)) {
                    handleInvalidField(this.fields[i], this.fields[i].validators[j].errorMsg);
                }
            }
        }
    }
    return errorsByField;
}

Form.Validator.prototype.getValueErrors = function(markErrors) {
    if (markErrors) {
        for (var i = 0; i < this.fields.length; ++i) {
            this.clearErrorsFromField(this.fields[i]);
        }
    }
    
    var valueErrorsByField = this.getValueErrorsByField(markErrors);
    var errors = [];
    var firstInvalidWidget;
    for (var i = 0; i < valueErrorsByField.length; ++i) {
        if (markErrors) {
            if (!firstInvalidWidget) {
                firstInvalidWidget = valueErrorsByField[i].field.widget;
                if (firstInvalidWidget.focus) {
                    firstInvalidWidget.focus();
                }
            }
            this.addErrorToField(valueErrorsByField[i].field, valueErrorsByField[i].errorMsg);
        }
        errors.push(valueErrorsByField[i].errorMsg);
    }
    
    return errors;
}

Form.Validator.prototype.reset = function() {
    for (var i = 0; i < this.fields.length; ++i) {
        var item = this.fields[i].widget;
        this.clearErrorsFromField(this.fields[i]);
        if (item.reset) {
            item.reset();
        } else if (item.tagName) {
            if (item.tagName.toLowerCase() == 'input' || item.tagName.toLowerCase() == 'textarea') {
                item.value = '';
            } else if (item.tagName.toLowerCase() == 'select') {
                item.selectedIndex = -1;
            }
        }
    }
}

Form.Validator.prototype.clearErrorsFromField = function(field) {
    if (field.widget.showsOwnErrors && field.widget.showsOwnErrors()) {
        return;
    }
    
    var domNode = field.widget.getDomNode ? field.widget.getDomNode() : field.widget;
    if (domNode && domNode.style) {
        domNode.style.backgroundColor = '';
    }
    if (field.labelElement) {
        field.labelElement.className = '';
    }
    if (field.errorElement) {
        if (field.errorElement.parentNode) {
            field.errorElement.parentNode.removeChild(field.errorElement);
        }
        field.errorElement = null;
    }
}

Form.Validator.prototype.getValue = function() {
    var returnObj = {};
    for (var i = 0; i < this.fields.length; ++i) {
        if (this.fields[i].name) {
            returnObj[this.fields[i].name] = DOMUtils.getValue(this.fields[i].widget);
        }
    }
    return returnObj;
}

Form.Validator.prototype.setValue = function(valuesHash) {
    var returnObj = {};
    for (var i = 0; i < this.fields.length; ++i) {
        if (this.fields[i].name && valuesHash[this.fields[i].name] !== undefined) {
            DOMUtils.setValue(this.fields[i].widget, valuesHash[this.fields[i].name]);
        }
    }
}

Form.Validator.prototype.addErrorToField = function(field, errorMsg, positionName) {
    if (field.widget.showsOwnErrors && field.widget.showsOwnErrors()) {
        // Each widget must manage its validation
        return;
    }

    if (field.labelElement) {
        field.labelElement.className = 'labelWithError';
    }
    if (field.errorElement) {
        if (errorMsg) {
            field.errorElement.appendChild(DOM.createElement('div', {}, [ errorMsg ]));
        }
    } else {
        var containerDomNode, nextSibling;
        var domNode = field.widget.getDomNode ? field.widget.getDomNode() : field.widget;
        if (field.errorContainer) {
            containerDomNode = field.errorContainer;
            nextSibling = null;
        } else if (field.widget.getErrorContainer) {
            containerDomNode = field.widget.getErrorContainer();
            nextSibling = null;
        } else {
            containerDomNode = domNode.parentNode;
            nextSibling = domNode.nextSibling;
        }
        
        if (domNode && domNode.style) {
            domNode.style.backgroundColor = '#FFEFEF';
            domNode.style.backgroundImage = 'none';
        }
        
        if (containerDomNode) {
            if (errorMsg) {
                containerDomNode.insertBefore(
                    field.errorElement = DOM.createElement('span', { 'class': 'errorMsg' }, [ DOM.createElement('span', { 'class': field.positionName ? field.positionName : null }, [ errorMsg ])]),
                    nextSibling
                );
            }
        }
    }
    if (this.onerror) {
        this.onerror(field, errorMsg);
    }
}

Form.Validator.prototype.showsOwnErrors = function() {
    return true;
}

Form.Required = function() {};

Form.Required.prototype.init = function(errorMsg) {
    this.errorMsg = errorMsg ? errorMsg : 'This field is required.';
}
Form.Required.prototype.isValid = function(widget) {
    var value = DOMUtils.getValue(widget);
    if (value === null) {
        return false;
    } else if (typeof value == 'string') {
        return !VAR.isWhitespace(value);
    } else if (typeof value == 'object') {
        if (typeof value.length != 'undefined') {
            return value.length != 0;
        } else {
            for (var prop in value) {
                return true;
            }
            return false;
        }
    } else {
        throw 'Unsopported type for Required validator';
    }
}

Form.EmailValidator = function() {};

Form.EmailValidator.prototype.init = function(errorMsg) {
    this.errorMsg = errorMsg ? errorMsg : 'This field requires a valid email address.';
}
Form.EmailValidator.prototype.isValid = function(widget) {
    return VAR.isEmail(DOMUtils.getValue(widget), true);
}

Form.NumberOrBlankValidator = function() {};

Form.NumberOrBlankValidator.prototype.init = function(errorMsg) {
    this.errorMsg = errorMsg ? errorMsg : 'This field should be a number.';
}
Form.NumberOrBlankValidator.prototype.isValid = function(widget) {
    var value = DOMUtils.getValue(widget);
    if (!value) {
        return VAR.isNumber(value);
    } else {
        return true;
    }
}

Form.ValidUrlValidator = function() {};

Form.ValidUrlValidator.prototype.init = function(errorMsg) {
    this.errorMsg = errorMsg ? errorMsg : 'This field requires a valid url.';
}
Form.ValidUrlValidator.prototype.isValid = function(widget) {
    return true; //This validator return always true for now.
    var filter = /^(http|https|ftp):\/\//;
    return filter.test(DOMUtils.getValue(widget)); 
}

Form.FileExtensionValidator = function() {};

Form.FileExtensionValidator.prototype.init = function(fileExtensions, errorMsg) {
    this.fileExtensions = fileExtensions ? fileExtensions : [ 'jpg', 'gif', 'bmp' ];
    for (var i = 0; i < this.fileExtensions.length; ++i) {
        if (!extensionText) {
            var extensionText = '';
            this.filter = '';
        } else {
            if (this.fileExtensions.length == i +1) {
                extensionText += ' or ';
            } else {
                extensionText += ', ';
            }
            this.filter += '|';
        }
        extensionText += this.fileExtensions[i];
        this.filter += this.fileExtensions[i];
    }
    this.errorMsg = errorMsg ? errorMsg : 'This file only supports ' + extensionText + ' extensions.';
}
Form.FileExtensionValidator.prototype.isValid = function(widget) {
    var value = DOMUtils.getValue(widget.fileInput);
    if (VAR.isWhitespace(value)) {
        return true;
    }
    var extension = value.substr(value.lastIndexOf('.')+1).toLowerCase();
    var filter = new RegExp('^(' + this.filter + ')');
    return filter.test(extension);
}