/*****************************************************************************************************
 * Javascript Form Validator
 * Versie 1.1
 *
 * De o-zo-geweldige Javascript form validator maakt het valideren van formulieren een stuk makkelijker,
 * gebruiksvriendelijker en mooier om te zien. Natuurlijk moet er nog steeds een form van server-side
 * controle uitgevoerd worden, maar aangezien het merendeel van de gebruikers Javascript-enabled is,
 * kunnen deze meldingen een stuk minder grafisch worden.
 *
 * Het valideren van de velden gaat aan de hand van classnames. Het is ook mogelijk om zelf een
 * validatie-regel toe te voegen d.m.v. de volgende code:
 *
 * Validator.registerSheet({ classname: function(element, parameters) {} });
 *
 * Een sheet kan meerdere regels bevatten.
 *
 * UPDATE HISTORY
 * ---------------------------------------------------------------------------------------------------
 * 26-11-2007 AR Basis opgezet nadat gekke Zend het hele bestand had leeggegooid.
 * 04-12-2007 AR Opschonen code en _isVisible() toegevoegd.
 * 
 *****************************************************************************************************/

String.prototype.stripSpaces = function() {
    var regex = new RegExp(/\s+/g);
    return this.replace(regex, '');
};

var Validator = Class.create();

/**
 * Globale foutmeldingen
 */
Validator.Errors = {
    ERR_EMPTY: 'Het veld moet ingevuld zijn',
    ERR_NOT_AN_EMAIL: 'Dit is geen geldig e-mailadres',
    ERR_NOT_A_POSTAL: 'Dit is geen geldige postcode',
    ERR_NOT_EQUALTO: 'De velden komen niet overeen',
    ERR_REQUIRED_ONE: 'Er moet ten minste 1 veld worden geselecteerd'
};

/** 
 * Globale validatie-regels
 */
Validator.GlobalSheet = {
    'required': function(element) {
        if (element.type == 'checkbox') {
            return element.checked;
        } else {
            return (element.value == '') ? Validator.Errors.ERR_EMPTY : true;
        }
    },
    'validate-postcode': function(element) {
        if (element.value != '') {
            element.value = element.value.stripSpaces();
            var Regex = new RegExp('([0-9]{4})([a-zA-Z]{2})');
            var result = Regex.exec(element.value);
            if (result) {
                element.value = result[1] + ' ' + result[2].toUpperCase();
                return true;
            } else {
                return Validator.Errors.ERR_NOT_A_POSTAL;
            }
        } else {
            return Validator.Errors.ERR_NOT_A_POSTAL;
        }
    },
    'validate-email': function(element) {
        var Regex = new RegExp('([a-zA-Z0-9\-_\._]+)@([a-zA-Z0-9\-\._]+)\.([a-zA-Z]{2,4})');
        var result = Regex.exec(element.value);
        if (result) {
            return true;
        } else {
            return Validator.Errors.ERR_NOT_AN_EMAIL;
        }
    },
    'validate-equalTo': function(element, params) {
        if (params.element && $(params.element)) {
            var referenceElement = $(params.element);
            if (element.value.toString() != referenceElement.value.toString()) {
                return Validator.Errors.ERR_NOT_EQUALTO;
            } else {
                return true;
            }
        }
    },
    'validate-require-one': function(element) {
        if (!element.checked || element.checked === false) {
            var formElements = element.form.elements;
            var checkedElementFound = false;
            if (formElements && formElements.length > 0) {
                for (var i = 0; fElement = formElements[i]; i++) {
                    if (fElement.getAttribute('type') == 'radio') {
                        if (fElement.name == element.name) {
                            if (fElement.checked) {
                                checkedElementFound = true;
                            }
                        }
                    }
                }
            }
            
            return (checkedElementFound === true) ? true : Validator.Errors.ERR_REQUIRED_ONE;
            
        } else {
            return true;
        }
    }
};

/**
 * Utility functions
 */
Validator.utils = {
    
    /**
     * Controleren of een formulier gevalideerd is
     */
    isValidated: function(form) {
        form = $(form);
        return form.hasClassName('validation-complete');
    },
    
    validationSuccess: function(form) {
        form = $(form);
        return form.hasClassName('validation-success');
    },
    
    validationFailed: function(form) {
        form = $(form);
        return form.hasClassName('validation-failed');
    },
    
    /**
     * Controleren of een formulier gevalideerd moet worden
     */
    requiresValidation: function(form) {
        form = $(form);
        return form.hasClassName('validate');
    }
    
};

Element.addMethods('form', Validator.utils);

/********************************************************************************************************************
 ******************************** CLASS HIERONDER: S.V.P OPPASSEN MET WIJZIGEN **************************************
 *******************************************************************************************************************/

Validator.prototype = {

    /**
     * De constructor
     *
     * @param string|element form
     */
    initialize: function(form) {
        
        form.validator = this;
        
        this.form = $(form);
        this.errors = [];
        this.sheets = [];
        this.elements = [];
        
        this.options = Object.extend({ 
            validateOnSubmit: true,                     // valideren wanneer het formulier verstuurd wordt?
            highlightOnError: true,                     // veld highlighten bij foutmeldingen?
            messageOnError: true,                       // textuele foutmelding per veld weergeven?
            summaryOnError: false,                      // foutopsomming weergeven?
            skipInvisibleElements: true,                // onzichtbare elementen overslaan?
            highlightClassName: 'errorField',           // classname voor foutmelding-velden
            messageClassName: 'errorMessage',           // classname voor foutmelding-berichten (textueel)
            onErrorFound: function() { },               // custom functie: wordt uitgevoerd bij een error
            onFieldValidated: function() { },           // custom functie: wordt uitgevoerd bij een succes
            onValidationComplete: function() { },       // custom functie: wordt uitgevoerd na validatie
            onValidationSuccess: function() { },        // custom functie: wordt uitgevoerd na succesvolle validatie
            onValidationFailed: function() { }          // custom functie: wordt uitgevoerd na foute validatie
        }, arguments[1] || {});
        
        this.sheets.push(Validator.GlobalSheet);
        
        if (this.options.validateOnSubmit) {
            Event.observe(this.form, 'submit', this.validate.bindAsEventListener(this));
        }
        
    },
    
    /**
     * Een validatie-sheet registreren
     * 
     * @param Object sheet
     */
    registerSheet: function(sheet) {
        this.sheets.push(sheet);
    },
    
    /**
     * Een formulier valideren
     *
     * @param event event
     */
    validate: function(event) {
        
        this.errorID = -1;        
        var inputs = this.elements = this.form.getElements();
        this.errorFound = false;
        
        for (var i = 0; element = inputs[i]; i++) {
            if (this.options.skipInvisibleElements) {
                if (this._isVisible(element)) {     
                    this.validateField.bind(this)(element);
                }
            } else {
                this.validateField.bind(this)(element);
            }
        }
        
        if (this.errorFound) {
            this._onValidationFailedInternal.bindAsEventListener(this)(event);
            this.options.onValidationFailed.bindAsEventListener(this)(event);
        } else {
            this._onValidationSuccessInternal.bindAsEventListener(this)(event);
            this.options.onValidationSuccess.bindAsEventListener(this)(event);
        }
        
        this.options.onValidationComplete.bindAsEventListener(this)(event);
        this._onValidationCompleteInternal.bindAsEventListener(this)(event);

    },

    /**
     * Een specifiek veld valideren
     */
    validateField: function(element) {
        
        element.errorFound = undefined;
        
        for (var s = 0; sheet = this.sheets[s]; s++) {
            
            for (expression in sheet) {
                
                if (Element.hasClassName(element, expression) && typeof(element.errorFound) == 'undefined') {
                    
                    var validation = sheet[expression](element, Behaviour.getParameters(element));
                    
                    if (validation !== true) {
                        
                        var errorID = this._createError(element, validation);
                        this.errorFound = true;
                        this.options.onErrorFound.bindAsEventListener(this);
                        
                    } else {
                        
                       if (typeof(element.errorID) != 'undefined') {
                           this._removeError.bind(this)(element.errorID);
                       }
                       
                       this.options.onFieldValidated.bindAsEventListener(this);                           
                       
                    }
                }
            }
        }        
    },
    
    /**
     * Controleren of een element zichtbaar is op het scherm
     */
    _isVisible: function(element) {
        
        if (element.style.display == 'none') return false;
        
        while (element = element.parentNode) {
            if (element.style && element.style.display == 'none') return false;
        }
        
        return true;
          
    },
    
    /**
     * Wordt uitgevoerd nadat het valideren niet gelukt is
     *
     * @param event event
     */
    _onValidationFailedInternal: function(event) { 
        event.stop();
        Element.addClassName(this.form, 'validation-failed');
    },
    
    /**
     * Wordt uitgevoerd nadat het valideren gelukt is
     *
     * @param event event
     */
    _onValidationSuccessInternal: function(event) { 
        Element.addClassName(this.form, 'validation-success');
    },
    
    /**
     * Wordt uitgevoerd nadat de validatieprocedure is uitgevoerd
     * ongeacht de uitkomst
     *
     * @param event event
     */
    _onValidationCompleteInternal: function(event) {
        Element.addClassName(this.form, 'validation-complete');
    },
    
    /**
     * Foutmelding ophalen aan de hand van errorID
     *
     * @param int errorID
     * @return int 
     */
    _getErrorById: function(errorID) {
        if (typeof(this.errors[errorID]) != 'undefined') {
            return this.errors[errorID];
        } else {
            return -1;
        }
    },
    
    /**
     * Een foutmelding creeeren
     *
     * @return int
     */
    _createError: function(element, message) {
        if (this.options.highlightOnError) {
            Element.addClassName(element, this.options.highlightClassName);
        }
        if (this.options.messageOnError && message != '') {
            var messageContainer = document.createElement('div');
            messageContainer.className = this.options.messageClassName;
            messageContainer.innerHTML = message;
            //element.parentNode.insertBefore(messageContainer, element.nextSibling);
            // TODO: input moet gewrapped worden in dit veld zodat we de melding beter kunnen positioneren
        }
        
        var errorID = ++this.errorID;

        this.errors[errorID] = {
            element: element,
            message: message
        };
        
        element.errorFound = true;
        element.errorID = errorID;
        
        return errorID;
        
    },
    
    /**
     * Error verwijderen
     *
     * @param int errorID
     */
    _removeError: function(errorID) {
        var error = this._getErrorById(errorID);
        if (error != -1) {
            Element.removeClassName(error.element, this.options.highlightClassName);
            error.element.errorID = undefined;
            this.errors.errorID = undefined;
        }
    }
    
};