// JScript File

// takes arguments, which should be objects created from json, and creates relevant validator class instances
ValidatorParser = Class.create({
    initialize: function(){
    },
    // gets argument on latter half of - of className, from elementClass
    getValidatorArgument: function(elementClass, className){
        var validatorArgument;
        if(!elementClass) return null;
        if(!elementClass.indexOf) return null;
        if(elementClass.indexOf(className) == -1) return null;
        // find argument on second side of dash
        // do this by first just finding starting point of validator class until last space
        var validatorStartingPoint = elementClass.indexOf(className);
        var tailHalfString = elementClass.substr(validatorStartingPoint, elementClass.length - validatorStartingPoint);
        var validatorEndingPoint = tailHalfString.indexOf(' ');
        var validatorString;
        if(validatorEndingPoint != -1)
            validatorString = tailHalfString.substr(0, validatorEndingPoint);
        else
            validatorString = tailHalfString;
        // now split validator class into classname on left and arguments on right side of dash.
        var validatorSections =  validatorString.split('-');
        if(validatorSections.length > 0)
            validatorArgument = validatorSections[1];
        else
            validatorArgument = null;
            
        // now replace &nbsp; with whitespaces in validatorArgument
        if(validatorArgument){
            validatorArgument = this.removeEscapedWhiteSpaces(validatorArgument);
        }
        
            
        return validatorArgument;  
    },
    removeEscapedWhiteSpaces: function(stringToFix){
        while(stringToFix.indexOf('&nbsp;') != -1)
            stringToFix = stringToFix.replace('&nbsp;', ' ');
        return stringToFix;
    },
    parseFieldValidator: function(validationSpan, form){
        var validationClass = validationSpan.className;
        var fieldAccessorArgumentJson = this.getValidatorArgument(validationClass, 'fieldToAccess');
        if(!fieldAccessorArgumentJson) return null;
        var fieldAccessorArgument = fieldAccessorArgumentJson.evalJSON();
         
        var fieldAccessor = this.parseFieldAccessor(fieldAccessorArgument, form);
        
        var valueValidator = this.parseValueValidator(validationClass);
        var validationError;
        var validationErrorArgument = this.getValidatorArgument(validationClass, 'validatorError');
        if(validationErrorArgument){
            var validationErrorArgumentObject = validationErrorArgument.evalJSON();
            validationError = this.parseValidationError(validationErrorArgumentObject);
        }
        else if(valueValidator)
            validationError = valueValidator.getDefaultValidationError();
            
        return new FieldValidator(validationSpan, fieldAccessor, valueValidator, validationError);
    },
    // returns either a single field or multi-field accessor
    parseFieldAccessor: function(accessorArgumentInfo, form){
        if(!accessorArgumentInfo) return false;
        // if type is single field validator, fieldId and fieldName will be here
        if(accessorArgumentInfo.singleField){
            return new FieldAccessor(accessorArgumentInfo.singleField, form);
        }
        // otherwise, go through fields and reparse
        else if(accessorArgumentInfo.multiField){
            var multiFieldArguments = accessorArgumentInfo.multiField;
            if(!multiFieldArguments.fields) return null;
            var fieldAccessors = new Array();
            var methodType = multiFieldArguments.methodType;
            for(var i = 0; i < multiFieldArguments.fields.length; i++){
                var fieldAccessor = this.parseFieldAccessor(multiFieldArguments.fields[i], form);
                fieldAccessors.push(fieldAccessor);
            }
            // create MultiFieldAccessor with all fieldAccessors
            return new MultiFieldAccessor(fieldAccessors, methodType);
        }
        else
            return null;
    },
    parseValueValidator: function(validationClass){
        if(!validationClass) return null;
        var valueValidator = null;
        
        if(validationClass.indexOf('required') != -1)
            valueValidator = new RequiredValidator();
        else if(validationClass.indexOf('integer') != -1)
            valueValidator = new IntegerValidator(); 
        else if(validationClass.indexOf('email') != -1)
            valueValidator = new EmailValidator();
        else if(validationClass.indexOf('length') != -1){        
            var lengthArgument = this.getValidatorArgument(validationClass, 'length');
            if(!lengthArgument) 
                valueValidator = null;
            else
                valueValidator = new LengthValidator(lengthArgument);
        }
        else if(validationClass.indexOf('compare') != -1){        
            var comparisonArgument = this.getValidatorArgument(validationClass, 'compare');
            if(!comparisonArgument) 
                valueValidator = null;
            else
                valueValidator = new ComparisonValidator(comparisonArgument);
        }  
           
        return valueValidator;
    },
    parseValidationError: function(validationErrorArgumentObject){
        if(!validationErrorArgumentObject) return null;
        return new ValidationError(validationErrorArgumentObject.errorTypeName, 
            validationErrorArgumentObject.errorMessage, 
            validationErrorArgumentObject.errorClassName, 
            validationErrorArgumentObject.validatorSymbol)
    }
});

var validatorParser = new ValidatorParser();

var Validator = Class.create({
    initialize: function(validationElement, form){
        this.validationElement = validationElement;
        this.form = form;
        this.elementToValidate = null;
        this.validationOrder = 0;
    },
    validate: function(){
        return true;
    }, 
    reflectValidation: function(){
    }
});

var FormValidator = Class.create(Validator, {
    initialize: function($super, form){
        // field validators will be validated
        // first build validation elements then call base constructor to set properties
        var validationElements = new Array();
        var validatorSpans = $$('span.validator');
        for(var i = 0; i < validatorSpans.length; i++){
            var fieldValidator = validatorParser.parseFieldValidator(validatorSpans[i], form);
            validationElements.push(fieldValidator);
        }        
        $super(validationElements, form); 
        this.validationErrors = new Hash();     
    },
    // old method
    validate: function(){
        var valid = true;
        var elementValid;
        this.clearValidationStatus();
        // cycle through each element and validate it
        for(var i = 0; i < this.validationElement.length; i++){
            var currentElement = this.validationElement[i];
            // check for method existence
            if(currentElement.validate){
                // set valid to false if element not valid and add to validation message
                elementValid = currentElement.validate();
                currentElement.reflectValidation(elementValid);
                if(!elementValid){
                    valid = false;
                    var validationError = currentElement.getValidationError();
                    // add validator error to list if not there already
                    if(!this.validationErrors.get(validationError.name))
                        this.validationErrors.set(validationError.name, validationError);
                }
            }
        }        
        return valid;
    },
    reflectValidation: function(){   
        var validationMessage = "";
        // need to hardcode keys cause of failure of foreach
        this.validationErrors.each(function(pair){
            validationMessage += pair.value.errorMessage  + '\n';
        });

        if(validationMessage != "")
            alert(validationMessage);
    },
    clearValidationStatus: function(){
        var validationErrorKeys = this.validationErrors.keys();
        for(var i = 0; i < validationErrorKeys.length; i++)
            this.validationErrors.unset(validationErrorKeys[i]);

    }
});

var FieldValidator = Class.create(Validator, {
    initialize: function(validationSpan, fieldAccessor, valueValidator, validationError){
        this.validationSpan = validationSpan;
        this.fieldAccessor = fieldAccessor;
        this.valueValidator = valueValidator;
        this.validationError = validationError;
    },
    initializeOld: function($super, validationElement, form){
        $super(validationElement, form);
        
        //valueValidator is what validates the value
        this.valueValidator=null;
        
        // now see if custom validation error appears in class.  if so, use it.  Otherwise, use
        // default value passed in.
        var validationError = null;
        var customValidationErrorArgument = this.getValidatorArgument(validationElement.className, 'validatorError');
        if(customValidationErrorArgument){
            var customValidationError = customValidationErrorArgument.evalJSON();
            validationError = new ValidationError(customValidationError.errorTypeName, 
                customValidationError.errorMessage.unescapeHTML(), customValidationError.errorClassName, customValidationError.validatorSymbol);
        }
        
        
        
        //find label which points to this element
        //this.label = $$('label[for="'+ validationElement.id +'"]');
    },
    validate: function(){
        var valid = true;
        if(!this.fieldAccessor || !this.valueValidator) return true;
        if(!this.fieldAccessor.isEnabled()) return true;
        var value = this.fieldAccessor.getValue();
        valid = this.valueValidator.validate(value);
        return valid;
    },
    validateOld: function(){
        var valid = true;
        //this.clearValidationStatus();
        if(this.valueValidator)
            valid =  this.valueValidator.validate();
        return valid;
    },
    reflectValidation: function(valid){
        if(!this.fieldAccessor || !this.validationError) return false;
        if(valid){
            this.clearValidationStatus();
        }
        else{
            // set fields to invalid
            this.fieldAccessor.setInvalid(this.validationError.errorClass);
            // add error symbol to validation span
            this.validationSpan.innerHTML = this.validationError.errorSymbol;
        }
        
    },
    clearValidationStatus: function(){        
        // set fields to valid
        this.fieldAccessor.setValid();
        // remove error symbol from validation span
        this.validationSpan.innerHTML = '';
    },  
    getValidationError: function(){
        return this.validationError;
    }
});

//fieldToAccess should be an object with properties name and id
var FieldAccessor = Class.create({
    initialize: function(fieldToAccess, form){
        this.form = form;
        this.fieldNameToAccess = fieldToAccess.name;
        this.fieldToAccess = $(fieldToAccess.id);
    },
    getValue: function(){
        if(!this.form[this.fieldNameToAccess]) return null;
        var value = null;
        // if checkbox, return if checked.  Otherwise, get value from form (use this method
        // because with radio buttons cannot get value of element id)
        if(this.fieldToAccess.type == "checkbox")
            value = this.fieldToAccess.checked;
        else {
            // if has value, then get value.  Otherwise try to go through elements and get checked
            // value
            var formElementToAccess = this.form[this.fieldNameToAccess];
            if(formElementToAccess.value)
                value = formElementToAccess.value;
            else if(formElementToAccess.length){
                for(var i = 0; i < formElementToAccess.length; i++){
                    if(formElementToAccess[i].checked){
                        value = formElementToAccess[i];
                        break;
                    }
                }
            }
        }
        return value;
    },
    isEnabled: function(){
        if(!this.fieldToAccess) return true;
        var enabled;
        if(this.fieldToAccess.disabled)
            enabled = false;
        else
            enabled = true;
        return enabled;
    },
    setInvalid: function(errorClassName){
        if(!this.fieldToAccess) return false;
        this.fieldToAccess.addClassName(errorClassName);
        this.errorClassName = errorClassName;
    },
    setValid: function(){
        if(!this.errorClassName || !this.fieldToAccess) return false;
        this.fieldToAccess.removeClassName(this.errorClassName);
    }
});

//contains an array of field accessors
var MultiFieldAccessor = Class.create({
    initialize: function(fieldAccessors, joinMethod){
        this.fieldAccessors = fieldAccessors;
        this.joinMethod = joinMethod;
    },
    getValue: function(){
        var value = null;
        if(!this.fieldAccessors) return false;
        
        var fieldValues = new Array();
        for(var i = 0; i < this.fieldAccessors.length; i++){
            var fieldValue = this.fieldAccessors[i].getValue();
            fieldValues.push(fieldValue);
        }
        
        if(this.joinMethod == 'concat'){
            value = "";
            for(var i = 0; i < fieldValues.length; i++){
                if(fieldValues[i] != null && fieldValues[i] != '')
                    value += fieldValues[i];
            }            
        }
        else if(this.joinMethod == 'equal' || this.joinMethod == 'notEqual'){
            var same = true;
            // first make sure fields arent empty
            if(!this.isBlank(fieldValues)){    
                for(var i = 0; i < fieldValues.length - 1; i++){
                    if(fieldValues[i] != fieldValues[i + 1]){
                        same = false;
                        break;
                    }
                }   
                if(this.joinMethod == 'equal')
                    value = same;
                else
                    value = !same;
            }
            else
                value = true;
        }
        
        return value;
    },
    isBlank: function(fieldValues){
        if(!fieldValues.length) return false;
        var blank = true;
        for(var i = 0; i < fieldValues.length; i++){
            if(fieldValues[i].blank)
                if(!fieldValues[i].blank()){
                    blank = false;
                    break;
                }
        }
        return blank;
    },
    isEnabled: function() {
        if(!this.fieldAccessors) return true;
        if(!this.fieldAccessors.length) return true;
        var enabled = true;
        for(var i = 0; i < this.fieldAccessors.length; i++){
            if(!this.fieldAccessors[i].isEnabled){
                enabled = false;
                break;
            }                
        }
        return enabled;
    },
    setInvalid: function(errorClassName){
        if(!this.fieldAccessors) return false;        
        for(var i = 0; i < this.fieldAccessors.length; i++){
            this.fieldAccessors[i].setInvalid(errorClassName);
        }
    },
    setValid: function(){
        if(!this.fieldAccessors) return false;        
        for(var i = 0; i < this.fieldAccessors.length; i++){
            this.fieldAccessors[i].setValid();
        }
    }
});

// validates a value
var ValueValidator = Class.create(Validator, {
    initialize: function(defaultValidationError){
        this.defaultValidationError = defaultValidationError;
    },
    validate: function(value){
        return true;
    },
    getDefaultValidationError: function(){
        return this.defaultValidationError;
    }
});

var RequiredValidator = Class.create(ValueValidator, {
    initialize: function($super){
        $super(new RequiredValidationError());
    },
    validate: function(value) {
        var valid;
        
        if(!value)
            valid = false;
        // if blank method appears then it is a string, check if empty
        // otherwise it is a boolean value so it is true.
        else if(value.blank){
            if(!value.blank())
                valid = true;
            else
                valid = false;
        }
        else
            valid = true;
        return valid;
    }
});

// abstract class that validates a value in a field to be correct
var CorrectValueValidator = Class.create(ValueValidator, {
    initialize: function($super, validationError){
        if(validationError)
            $super(validationError)
        else
            $super(new InvalidValueValidationError());
    },
    validate: function(value){
        // first make sure value is filled.  If it is, then perform value validation
        //var value = this.getValue();
        var valid;
        if(value != '' && value != null)
            valid = this.validateValue(value);
        else
            valid = true;
        return valid;
    },
    validateValue: function(value){
        // should be overwritten by subclasses
        return true;
    }
});

var IntegerValidator = Class.create(CorrectValueValidator, {
    validateValue: function(value) {
        var valid;
        var integerFilter=/^\$?\d{1,3}(,?\d{3})*(\.\d{1,2})?$/
        if(integerFilter.test(value)) 
            valid = true;
        else
            valid = false;
        return valid;
    }
});

var EmailValidator = Class.create(CorrectValueValidator, {
    validateValue: function(value) {
        var valid;
        var emailFilter=/^\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
        if(emailFilter.test(value)) 
            valid = true;
        else
            valid = false;
            
        return valid;
    }
});

var LengthValidator = Class.create(CorrectValueValidator, {
    initialize: function($super, validationLengthArgument){
        if(!validationLengthArgument) return false;
        $super(new LengthValidationError());
        this.validLength = parseInt(validationLengthArgument);
    },
    validateValue: function(value) {
        var valid;
        if(value.length == this.validLength){
            valid = true;
        }
        else
            valid = false;
            
        return valid;
    }
});

var ComparisonValidator = Class.create(CorrectValueValidator, {
    initialize: function($super, comparisonArgument){
        if(!comparisonArgument) return false;
        $super();
        if(comparisonArgument == "true")
            this.shouldBeSame = true;
        else
            this.shouldBeSame = false;
    },
    // value will be true if the same, false if different
    validateValue: function(value) {
        // comparison built into field accessor
        if(value)
            return true;
        else
            return false;
    }
});


var ValidationError = Class.create({
    initialize: function(name, errorMessage, errorClass, errorSymbol){
        this.name = name;
        this.errorMessage = errorMessage;
        this.errorClass = errorClass;
        this.errorSymbol = errorSymbol;
    }
});

var RequiredValidationError = Class.create(ValidationError,{
    initialize: function($super){
        $super("required", "* You must select at least one radio button and input your email to proceed.", "errorRequireds", "*");
    }
});

var InvalidValueValidationError = Class.create(ValidationError,{
    initialize: function($super){
        $super("invalid", "* Please correct all invalid fields (marked in yellow)", "errorInvalid", "*");
    }
});

var LengthValidationError = Class.create(ValidationError,{
    initialize: function($super){
        $super("length", "* Please make sure all fields have a valid length (marked in yellow)", "errorLength", "*");
    }
});