/**
 * TODO: Be able to handle multiple pages, build in the ability to scroll using the scroller class - rename that sucker to form scroller or something
 * If the form is more than one page, there is a continue button that the user must press to scroll to the next section, validation occurs on that whole form page
 * If the form is one page than the submit button causes a client side validation to occur on the whole form, then send to the server, use the servers response
 * to either show failed validation or redirect to a new page
 * TODO: Build in the ability to indicate an ajax request to validate a field using class names
 */

/**
 * Creates a validation with a specific form. FormElements within that form can be manual validated or automatically
 * validated when their values change or their form is submitted. Custom validation methods can be passed to the object along
 * with custom error handling. Based on the work of Russell S. Ahlstrom.
 *
 * Requires prototype.js library.
 */
var ProtoForm = Class.create({
    /**
     * Constructor
     */
    initialize: function(formId, options) {
        // Update the options hash
        this.options = $H({
            onChange: true, // boolean onChange - Validate each form field when its value changes. Defaults to true.
            onSubmit: true, // boolean onSubmit - Validate everything in the validator's form on a form submit. Defaults to false.
            onSubmitStart: false, // function onSubmitStart - Function called before form submit begins validation. Defaults to false. This can be used to clear global variables before beginning validation.
            onSubmitFinish: false // function onSubmitFinish - Function called after form submits ends. Defaults to false. Passes a boolean of true/false depending on success stat. This can be used to add your own custom onSubmit actions. Return false in order to cancel the form submission.
        });
        this.options.update(options);

        // Class variables
        this.form = $(formId);
        this.errorArray = [];
        this.validationRunning = false;
        this.validatingAll = false;
        this.serverSideValidationPassed = true;
        this.validationQueue = [];
        this.componentsChecked = [];
        this.componentsPassedValidation = [];
        this.currentComponent = null;
        this.clickEventFunction = this.clickEvent.bindAsEventListener(this);
        this.changeEventFunction = this.changeEvent.bindAsEventListener(this);
        this.submitEventFunction = this.submitEvent.bindAsEventListener(this);

        // Submit button and response variables
        this.submitButton = null;
        this.submitResponseDiv = null;
        this.originalSubmitButtonValue = null;

        // Build an array of all of the components
        this.componentArray = this.form.getElementsBySelector('div.component');

        if(this.options.get('onChange')) {
            this.addOnChangeFunctions();
        }
        if(this.options.get('onSubmit')) {
            this.addOnSubmitFunction();
        }

        this.addHighlightComponentListeners();

        var that = this;
        this.componentArray.each(function(component) {
            // Add any captcha
            if(component.hasClassName('type-Captcha')) {
                component.getElementsBySelector('img')[0].observe('click', function() {
                    component.getElementsBySelector('img')[0].src = "/php/classes/captcha.php?" + Math.floor(Math.random() * 10001);
                    component.getElementsBySelector('input')[0].clear().focus();
                });
            }

            // Add any password strength meters
            if(component.hasClassName('validation-passwordStrengthMeter')) {
                var passwordElement = component.down('input');

                var title = passwordElement.previous("label").innerHTML.stripTags().replace(" *", "").strip();

                var tip = new Element("div", {
                    "id": passwordElement.id + "-tip",
                    "style": "display: none;"
                });
                $(passwordElement).up().insert(tip);
                var passwordStrength = new Element("div", {
                    "id": passwordElement.id + "-strength"
                    }).update("Strength: <b>" + that.getPasswordStrength(passwordElement.getValue()).strength + "</b>");
                $(passwordElement.id + '-tip').insert(passwordStrength);

                new Tip(component, $(passwordElement.id + "-tip"), {
                    delay: 0,
                    title: title,
                    showOn: 'component:highlighted',
                    hideOn: 'component:notHighlighted',
                    hideOthers: true,
                    style: 'protogrey',
                    stem: 'leftMiddle',
                    hook: {
                        target: 'rightMiddle',
                        tip: 'leftMiddle'
                    },
                    target: passwordElement
                });

                passwordElement.observe('keyup', function() {
                    var passwordStrength = new Element("div", {
                        "id": passwordElement.id + "-strength"
                        }).update("Strength: <b>" + that.getPasswordStrength(passwordElement.getValue()).strength + "</b>");
                    $(passwordElement.id + '-strength').update(passwordStrength);
                    if(passwordElement.getValue().length >= 4) {
                        that.validate(passwordElement.up());
                    }
                });
            }

        });
    },

    /**
     * Add listeners to each component that will highlight them when they are focused on
     */
    addHighlightComponentListeners: function() {
        var that = this;
        this.componentArray.each(function(component) {
            component.getElementsBySelector('input', 'textarea', 'select').each(function(field) {
                field.observe('focus', function(event) {
                    that.highlightComponent(component.id);
                });
            });
        });
    },

    /**
     * Highlight a component
     */
    highlightComponent: function(componentId) {
        this.currentComponent = $(componentId);
        $$('div.component').each(function(component) {
            if(componentId == component.id) {
                component.addClassName('highlight');
                component.fire("component:highlighted", component);
            }
            else {
                component.removeClassName('highlight');
                component.fire("component:notHighlighted", component);
            }
        });
    },

    /**
     * Add the on submit function
     */
    addOnSubmitFunction: function() {
        this.form.observe("submit", this.submitEventFunction);
    },

    /**
     * Add the on change function
     */
    addOnChangeFunctions: function() {
        this.form.observe('click', this.clickEventFunction);
        if(!Prototype.Browser.IE) {
            this.form.observe('change', this.changeEventFunction);
        }
        else {
            this.form.select('input').invoke('observe', 'change', this.changeEventFunction);
        }
    },

    clickEvent: function(event) {
        var validTags = ['input'];
        var validTypes = ['radio', 'checkbox'];
        var formElement = event.element();
        if(validTags.indexOf(formElement.tagName.toLowerCase()) == -1) {
            return;
        }
        if(validTypes.indexOf(formElement.type.toLowerCase()) != -1) {
            this.validate(formElement.up());
        }
    },

    changeEvent: function(event) {
        var validTags = ['input', 'select', 'textarea'];
        var invalidTypes = ['radio', 'checkbox'];
        var formElement = event.element();
        if(validTags.indexOf(formElement.tagName.toLowerCase()) == -1) {
            return;
        }
        if(formElement.tagName.toLowerCase() == "input" && invalidTypes.indexOf(formElement.type.toLowerCase()) != -1) {
            return;
        }
        if(formElement.up().hasClassName("type-Date") && !formElement.up().hasClassName("validationFailed")) {
            return;
        }
        this.validate(formElement.up());
    },

    submitEvent: function(event) {
        // Stop the submission event no matter what
        event.stop();

        this.highlightComponent(null);

        if(this.options.get('onSubmitStart')) {
            eval(this.options.get('onSubmitStart'));
        };

        var clientSideValidationPassed = this.validateAll();

        if(this.options.get('onSubmitFinish') != "") {
            var onSubmitFinishResult = eval(this.options.get('onSubmitFinish'));

            if(onSubmitFinishResult != false) {
                this.submitForm();
            }
        }
        else {
            // Restart the submission event if the form passed validation
            if(clientSideValidationPassed) {
                this.serverSideValidationPassed = true;
                this.submitForm(event);
            }
            else {
                this.focusOnFirstFailedComponent();
            }

            // Focus on failed components after server side validation
            if(!this.serverSideValidationPassed) {
                this.focusOnFirstFailedComponent();
            }
        }
    },

    focusOnFirstFailedComponent: function() {
        for(var i = 0; i < this.componentArray.length; i++) {
            if(this.componentArray[i].hasClassName("validationFailed")) {
                this.highlightComponent(this.componentArray[i]);
                if(this.componentArray[i].prototip) {
                    this.componentArray[i].prototip.show();
                }
                else {
                    //console.log("No tip on: " + this.componentArray[i].id);
                }
                //TODO: Replace with smooth scrolling at some point
                this.componentArray[i].scrollTo();
                if(this.componentArray[i].viewportOffset().last() <= 40) {
                    window.scrollBy(0, -40);
                }
                if(this.componentArray[i].hasClassName('type-SingleLineText')) {
                    this.componentArray[i].down("input").activate();
                }
                break;
            }
        }
    },

    submitForm: function(event) {
        // Wrap all of the form responses into an object based on the component type
        var hash = $H({});
        var self = this;
        var fileComponents = Array();
        this.componentArray.each(function(component) {
            var componentValue = "";
            var componentId = component.id.replace("-div", "");
            if(component.hasClassName('type-SingleLineText') || component.hasClassName('type-Hidden') || component.hasClassName('type-Captcha')) {
                //hash[componentId] = addslashes(component.down('input').getValue());
                componentValue = "$H({\"" + componentId + "\": \"" + addslashes(component.down('input').getValue()) + "\"})";
                componentValue = eval(componentValue);
              
                hash.update(componentValue);
            }
            else if(component.hasClassName('type-MultipleChoice')) {
                // Put all of the checked boxes into an array
                if(component.down("input").type == "checkbox") {
                    var inputsChecked = [];
                    component.getElementsBySelector('input').each(function(input) {
                        if(input.checked) {
                            inputsChecked.push(input);
                        }
                    });
                    componentValue = "[";
                    for(var i = 0; i < inputsChecked.length; i++) {
                        if(i == inputsChecked.length - 1) {
                            componentValue += "\"" + inputsChecked[i].getValue() + "\"";
                        }
                        else {
                            componentValue += "\"" + inputsChecked[i].getValue() + "\", ";
                        }
                    }
                    componentValue += "]";
                    componentValue = "$H({\"" + componentId + "\": " + componentValue + "})";
                }
                else {
                    componentValue = self.getRadioButtonValue(component.down("input"), component.down("label").innerHTML.stripTags().replace(" *", "").strip());
                    componentValue = "$H({\"" + componentId + "\": \"" + componentValue + "\"})";
                }
                componentValue = eval(componentValue);

                hash.update(componentValue);
            }
            else if(component.hasClassName('type-DropDown')) {
                componentValue = "$H({\"" + componentId + "\": \"" + addslashes(component.down('select').getValue()) + "\"})";
                componentValue = eval(componentValue);

                hash.update(componentValue);
            }
            else if(component.hasClassName('type-Date')) {
                componentValue = "$H({\"" + component.id.replace("-div", "") + "\": \"" + $(componentId + "-yyyy").getValue() + "-" +$(componentId + "-mm").getValue() + "-" +$(componentId + "-dd").getValue() + "\"})";
                componentValue = eval(componentValue);

                hash.update(componentValue);
            }
            // Copy of all of the file components for submission
            if(component.hasClassName('type-File') || component.hasClassName('type-UserImage')) {
                fileComponents.push(component.cloneNode(true));
            }
        });

        // Use a temporary form targeted to the iframe to submit the results
        var formClone = this.form.cloneNode(true); // Kage bunshin no jutsu!
        formClone.id = formClone.id+"-clone";
        formClone.hide(); // Hide from the battle
        formClone.innerHTML = ""; // Clear all of the existing components
        formClone.insert(new Element("input", { // Set all non-file values in one form object
            "name": "form",
            "value": hash.toJSON()
        }));
        fileComponents.each(function(component) { // Add any file components for submission
            formClone.insert(component)
        });
        this.form.insert(formClone); // Gotta be on the DOM in order to submit()
        formClone.submit();
        formClone.remove(); // Ninja vanish!
		//console.log(hash);

        // Find the submit button and the submit response
        this.submitButton = this.form.getElementsBySelector("input.submit").first();
        this.submitResponseDiv = this.submitButton.next(".submitResponse");
        this.originalSubmitButtonValue = this.submitButton.value;
        // Style the button
        this.submitButton.value = "Processing...";
        this.submitButton.disabled = true;

        this.submitResponseDiv.hide();
        
        // Show the form processing request
        //this.submitResponseDiv.update("<p><img src=\"/images/ajax-loader.gif\" /> Processing...</p>");

        this.checkFormSubmissionProgress();
    },

    checkFormSubmissionProgress: function() {
        var errorMessage = "";
        var self = this;
        var url = this.form.action;

        // Create an AJAX periodical updater to check to see if the form has been processed
        var formSubmissionProgressChecker = new Ajax.PeriodicalUpdater($(self.form.id + '-iframe'), url, {
            parameters: {"task": "getFormStatus"},
            frequency: 3,
            onSuccess: function(transport) {
                var json = transport.responseText.evalJSON();

                // The form was submitted successfully and passed validation
                if(json.status == "Form successfully processed." && json.validationResponse.success) {
                    // Stop the checker
                    formSubmissionProgressChecker.stop();
                    self.serverSideValidationPassed = true;
                    if(json.response.javascript != "") {
                        eval(json.response.javascript);
                    }

                    if(!json.response.success) { // Just update the response text if the action response wasn't completed successfully
                        self.formSuccessfullyProcessed(true, json.response.message);
                    }
                    else {
                        self.formSuccessfullyProcessed(true, json.response.message);
                        self.form.update(json.response.message);
                    }
                }
                // The form was not submitted successfully and validation failed
                else if(json.status == "Validation complete..." && !json.validationResponse.success) {
                    //console.log("Validation complete and failed.");
                    formSubmissionProgressChecker.stop();
                    json.validationResponse.componentsThatFailedValidation.each(function(component) {
                        self.validationFailed($(component.componentId + "-div"), component.errorArray);
                        self.errorHandler($(component.componentId + "-div"), component.errorArray);
                        self.serverSideValidationPassed = false;
                    });
                    
                    self.formSuccessfullyProcessed(false, errorMessage);
                }
                // The form is still in the process of being submitted
                else if(!json.status != "Form successfully processed.") {
                    //console.log("The form is still being processed.");
                    self.submitResponseDiv.update(json.status);
                    self.submitResponseDiv.show();
                }
                // The form was not submitted successfully
                else {
                    //console.log("The form was not submitted successfully.");
                    formSubmissionProgressChecker.stop();
                    if(json.response.message) {
                        self.submitResponseDiv.update(json.response.message);
                        self.submitResponseDiv.show();
                    }
                }
            },
            onFailure: function(transport) {
                //console.log("Failure");
                if(transport.responseText) {
                    var json = transport.responseText.evalJSON();
                    errorMessage = json.response.message;
                }
                formSubmissionProgressChecker.stop();
                self.formSuccessfullyProcessed(false, errorMessage);
            },
            onException: function(transport) {
                //console.log("Exception");
                if(transport.responseText) {
                    var json = transport.responseText.evalJSON();
                    errorMessage = json.response.message;
                }
                formSubmissionProgressChecker.stop();
                self.formSuccessfullyProcessed(false, errorMessage);
            }
        });
    },

    formSuccessfullyProcessed: function(success, message) {
        this.submitButton.value = this.originalSubmitButtonValue;
        this.submitButton.disabled = false;

        if(!success) {
            this.focusOnFirstFailedComponent();
        }
        if(message) {
            this.submitResponseDiv.update(message);
            this.submitResponseDiv.show();
        }
    },

    prevalidate: function(component) {
        var componentId = component.id.replace("-div", "");
        // Remove any error lists from the tip
        if($(componentId + "-tipErrorList")) {
            $(componentId + "-tipErrorList").remove();
        }
        if($(componentId + "-div").prototip && $(componentId + "-tip").hasClassName("temporary")) {
            //console.log("Deleting tip from " + componentId);
            $(componentId + "-div").prototip.remove();
        }
    },
    
    prevalidateAll: function() {
        var that = this;

        this.componentArray.each(function(component) {
            that.prevalidate(component);
        });
    },

    errorHandler: function(component, errorArray) {
        // This is the default error handler and is used if another is not specified.
        var componentId = component.id.replace("-div", "");

        //var errorTag = new Element("span", {
        //    "id": formElementId + "_errorText",
        //    "class": "validationFailedText"
        //}).update(fieldName + " " + errors.message[0]);
        //formElement.up(":not(label)").insert(errorTag);

        // Check to see if there is a tip associated with the item and if there is a tip update the error text

        var errorList = new Element("ul", {
            "id": componentId + "-tipErrorList",
            "class": "tipErrorList"
        });
        errorArray.each(function(error) {
            var errorItem = new Element("li").update(error);
            errorList.insert(errorItem);
        });

        if($(componentId + '-tip') && component.prototip != null) {
            $(componentId + '-tip').insert(errorList);
        }
        else {
            //console.log("Need to create tip for: " + componentId);
            var tipTarget = componentId;
            if(component.hasClassName("type-MultipleChoice")) {
                tipTarget = component.id;
            }
            if(component.hasClassName("type-Date")) {
                tipTarget = component.getElementsBySelector('select').last();
            }

            var title = $(componentId + "-div").down("label").innerHTML.stripTags().replace(" *", "").strip();
            var tip = new Element("div", {
                "id": componentId + "-tip",
                "style": "display: none; margin-top: -1em;",
                "class": "temporary"
            }).update(errorList);
            $(component).insert(tip);

            var newTip = new Tip(componentId + "-div", $(componentId + "-tip"), {
                delay: 0,
                title: title,
                showOn: 'component:highlighted',
                hideOn: 'component:notHighlighted',
                hideOthers: true,
                style: 'protogrey',
                stem: 'leftMiddle',
                hook: {target: 'rightMiddle', tip: 'leftMiddle'},
                target: tipTarget
            });
            //$(componentId + "-div").prototip.show();
            if(this.currentComponent) { // Make sure to always show the tip of the currently highlighted component
                this.currentComponent.prototip.show();
            }

        }
    },

    getElementName: function(formElement) {
        var sameNameTypes = ['radio', 'checkbox'];
        if(formElement.tagName.toLowerCase() == "input" && sameNameTypes.indexOf(formElement.type.toLowerCase()) != -1) {
            var formElementId = formElement.name;
        }
        else {
            var formElementId = (formElement.id) ? formElement.id : formElement.name;
        }
        return formElementId;
    },

    validationFailed: function(component, errorMessage) {
        component.addClassName('validationFailed');
        component.removeClassName('validationPassed');
        this.componentsPassedValidation = false;
        this.errorArray.push(errorMessage);
    },

    validationPassed: function(component) {
        component.removeClassName('validationFailed');
        component.addClassName('validationPassed');
    },

    clearValidation: function() {
        this.componentsPassedValidation = true;
        this.errorArray = [];
    },

    getValidation: function() {
        return this.componentsPassedValidation;
    },

    getErrors: function() {
        return this.errorArray;
    },

    validateAll: function() {
        // If an onChange validation is running, consider it not to be running to avoid conflicts between an onChange check and an onSubmit check.
        this.validationRunning = false;
        this.validatingAll = true;
        var passedValidation = true;

        var that = this;
        this.componentArray.each(function(component) {
            if(!that.validate(component)) {
                passedValidation = false;
            }
        });

        this.validatingAll = false; // Reset the validating all switch

        return passedValidation;
    },

    validate: function(component) {
        component = $(component);

        // If there is a validation running, queue the next component requesting validation
        if(this.validationRunning) {
            // Only queue the next component requesting validation if it hasn't already been validated
            if(this.componentsChecked.indexOf(component) == -1 && this.validationQueue.indexOf(component) == -1) {
                this.validationQueue.push(component);
            }
            return true;
        }
        this.validationRunning = true;

        var passedValidation = this.runValidation(component);

        // Validate everything in the queue directly
        while(this.validationQueue.length > 0) {
            this.runValidation($(this.validationQueue.shift()));
        }

        // Clear validation running flag and the list of elements checked this run through
        this.validationRunning = false;
        this.componentsChecked = [];

        return passedValidation;
    },

    runValidation: function(component) {
        component = $(component);
        var componentType = this.parseTextFromClassName(component, "type-");

        // Mark this component as checked
        this.componentsChecked.push(component);
        this.clearValidation();

        this.prevalidate(component);

        if(componentType == "SingleLineText" || componentType == "Captcha") {
            this.runValidationForStrings(component, component.className, component.down("label").innerHTML.stripTags().replace(" *", "").strip(), component.down('input').getValue());
        }
        else if(componentType == "MultipleChoice") {
            this.runValidationForMultipleChoice(component, component.className, component.down("label").innerHTML.stripTags().replace(" *", "").strip());
        }
        else if(componentType == "Date") {
            this.runValidationForDate(component, component.className, component.down("label").innerHTML.stripTags().replace(" *", "").strip());
        }

        if(this.getValidation() == false) {
            this.errorHandler(component, this.getErrors());
        }

        if(this.getValidation()) {
            this.validationPassed(component);
        }

        return this.getValidation();
    },

    runValidationForStrings: function(component, validations, label, value) {
        //console.log(component);
        //console.log(validations);
        //console.log(label);
        //console.log(value);

        if(validations.indexOf("validation-required") != -1) {
            if(!this.isRequired(value)) {
                this.validationFailed(component, "Required.");
            }
        }

        if(validations.indexOf("validation-blankValue") != -1) {
            if(!this.isBlank(value)) {
                this.validationFailed(component, "Must be blank.");
            }
        }

        if(validations.indexOf("validation-username") != -1) {
            if(!this.isUsername(value)) {
                this.validationFailed(component, "Must use 4 to 32 characters and start with a letter.");
            }
        }

        if((validations.indexOf("validation-usernameAvailable") != -1) && !this.validatingAll) {
            this.usernameIsAvailable(value, component);
        }
		
        if((validations.indexOf("validation-teamNameAvailable") != -1) && !this.validatingAll) {
            this.teamNameIsAvailable(value, component);
        }		

        if((validations.indexOf("validation-emailAvailable") != -1) && !this.validatingAll) {
            this.emailIsAvailable(value, component);
        }

        if(validations.indexOf("validation-password") != -1) {
            if(!this.isPassword(value)) {
                this.validationFailed(component, "Must use 4 to 32 characters.");
            }
        }

        if(validations.indexOf("validation-matches") != -1) {
            if(!this.matchesField(component, value)) {
                this.validationFailed(component, "Must match the previous field.");
            }
        }

        if(validations.indexOf("validation-birthday") != -1) {
            if(!this.is_birthday(value)) {
                this.validationFailed(component, "Must be a valid birthday.");
            }
        }

        if(validations.indexOf("validation-numericValue") != -1) {
            if(!this.isNumeric(value)) {
                this.validationFailed(component, "Must be a number.");
            }
        }

        if(validations.indexOf("validation-numericPositiveValue") != -1) {
            if(!this.isNumericPositive(value)) {
                this.validationFailed(component, "Must be a positive number.");
            }
        }

        if(validations.indexOf("validation-numericZeroPositiveValue") != -1) {
            if(!this.isNumericZeroPositive(value)) {
                this.validationFailed(component, "Must be zero or a positive number.");
            }
        }

        if(validations.indexOf("validation-numericNegativeValue") != -1) {
            if(!this.isNumericNegative(value)) {
                this.validationFailed(component, "Must be a negative number.");
            }
        }

        if(validations.indexOf("validation-numericZeroNegativeValue") != -1) {
            if(!this.isNumericZeroNegative(value)) {
                this.validationFailed(component, "Must be zero or a negative number.");
            }
        }

        if(validations.indexOf("validation-integerValue") != -1) {
            if(!this.isInteger(value)) {
                this.validationFailed(component, "Must be a whole number.");
            }
        }

        if(validations.indexOf("validation-integerPositiveValue") != -1) {
            if(!this.isIntegerPositive(value)) {
                this.validationFailed(component, "Must be a positive whole number.");
            }
        }

        if(validations.indexOf("validation-integerZeroPositiveValue") != -1) {
            if(!this.isIntegerZeroPositive(value)) {
                this.validationFailed(component, "Must be zero or a positive whole number.");
            }
        }

        if(validations.indexOf("validation-integerNegativeValue") != -1) {
            if(!this.isIntegerNegative(value)) {
                this.validationFailed(component, "Must be a negative whole number.");
            }
        }

        if(validations.indexOf("validation-integerZeroNegativeValue") != -1) {
            if(!this.isIntegerZeroNegative(value)) {
                this.validationFailed(component, "Must be zero or a negative whole number.");
            }
        }

        if(validations.indexOf("validation-alphaValue") != -1) {
            if(!this.isAlpha(value)) {
                this.validationFailed(component, "Must only contain alphabetic characters.");
            }
        }

        if(validations.indexOf("validation-alphanumericValue") != -1) {
            if(!this.isAlphanumeric(value)) {
                this.validationFailed(component, "Must only contain alphanumeric characters.");
            }
        }

        if(validations.indexOf("validation-dateValue") != -1) {
            if(!this.isDate(value)) {
                this.validationFailed(component, "Must be a date in the mm/dd/yyyy format.");
            }
        }

        if(validations.indexOf("validation-timeValue") != -1) {
            if(!this.isTime(value)) {
                this.validationFailed(component, "Must be a time in the hh:mm:ss tt format. ss and tt are optional.");
            }
        }

        if(validations.indexOf("validation-datetimeValue") != -1) {
            if(!this.isDateTime(value)) {
                this.validationFailed(component, "Must be a date in the mm/dd/yyyy hh:mm:ss tt format. ss and tt are optional.");
            }
        }

        if(validations.indexOf("validation-urlValue") != -1) {
            if(!this.isUrl(value)) {
                this.validationFailed(component, "Must be a valid Internet address.");
            }
        }

        if(validations.indexOf("validation-ssnValue") != -1) {
            if(!this.isSsn(value)) {
                this.validationFailed(component, "Must be a valid US Social Security Number.");
            }
        }

        if(validations.indexOf("validation-email") != -1) {
            if(!this.isEmail(value)) {
                this.validationFailed(component, "Must be a valid e-mail address.");
            }
        }

        if(validations.indexOf("validation-zipValue") != -1) {
            if(!this.isZip(value)) {
                this.validationFailed(component, "Must be a valid US zip code.");
            }
        }

        if(validations.indexOf("validation-canadianPostalValue") != -1) {
            if(!this.isCanadianPostal(value)) {
                this.validationFailed(component, "Must be a valid Canadian postal code.");
            }
        }

        if(validations.indexOf("validation-ukPostalValue") != -1) {
            if(!this.isUkPostal(value)) {
                this.validationFailed(component, "Must be a valid UK postal code.");
            }
        }

        if(validations.indexOf("validation-postalZipValue") != -1) {
            if(!this.isPostalZip(value)) {
                this.validationFailed(component, "Must be a valid US Zip Code, Canadian postal code, or UK postal code.");
            }
        }

        if(validations.indexOf("validation-phoneValue") != -1) {
            if(!this.isPhone(value)) {
                this.validationFailed(component, "Must be a valid US phone number.");
            }
        }

        if(validations.indexOf("validation-isbnValue") != -1) {
            if(!this.isIsbn(value)) {
                this.validationFailed(component, "Must be a valid ISBN and consist of either ten or thirteen characters.");
            }
        }

        if(validations.indexOf("validation-moneyValue") != -1) {
            if(!this.isMoney(value)) {
                this.validationFailed(component, "Must be a valid dollar value.");
            }
        }

        if(validations.indexOf("validation-moneyPositiveValue") != -1) {
            if(!this.isMoneyPositive(value)) {
                this.validationFailed(component, "Must be a valid positive dollar value.");
            }
        }

        if(validations.indexOf("validation-moneyZeroPositiveValue") != -1) {
            if(!this.isMoneyZeroPositive(value)) {
                this.validationFailed(component, "Must be zero or a valid positive dollar value.");
            }
        }

        if(validations.indexOf("validation-moneyNegativeValue") != -1) {
            if(!this.isMoneyNegative(value)) {
                this.validationFailed(component, "Must be a valid negative dollar value.");
            }
        }

        if(validations.indexOf("validation-moneyZeroNegativeValue") != -1) {
            if(!this.isMoneyZeroNegative(value)) {
                this.validationFailed(component, "Must be zero or a valid negative dollar value.");
            }
        }

        if(validations.indexOf("validation-isLength") != -1) {
            if(!this.isLength(value)) {
                this.validationFailed(component, "Must be exactly " + component.maxLength + " characters long.");
            }
        }

        if(validations.indexOf("validation-maxLength") == 0 || validations.indexOf(" validation-maxLength") > 0) {
            if(!this.isMaxLength(value)) {
                var maxLength = this.parseTextFromClassName(component, "validation-maxLength");
                if(maxLength == -1) {
                    maxLength = component.maxLength;
                }
                this.validationFailed(component, "Must be less than " + maxLength + " characters long.");
            }
        }

        if(validations.indexOf("validation-minLength") == 0 || validations.indexOf(" validation-minLength") > 0) {
            if(!this.isMinLength(value)) {
                var minLength = this.parseTextFromClassName(component, "validation-minLength");
                this.validationFailed(component, "Must be at least " + minLength + " characters long.");
            }
        }

        if(validations.indexOf("validation-maxFloat") == 0 || validations.indexOf(" validation-maxFloat") > 0) {
            if(!this.isMaxFloat(value)) {
                var maxFloat = this.parseTextFromClassName(component, "validation-maxFloat");
                this.validationFailed(component, "Must be numberic and cannot have more than " + maxFloat + " decimal place(s).");
            }
        }
    },

    runValidationForMultipleChoice: function(component, validations, label) {
        if(validations.indexOf("validation-required") != -1) {
            if(!this.multipleChoiceIsRequired(component, component.down('input'), component.down('input').name)) {
                this.validationFailed(component, "Required.");
            }
        }

        if(validations.indexOf("validation-minOptionChoice") == 0 || validations.indexOf(" validation-minOptionChoice") > 0) {
            if(!this.isMinOptionChoice(component)) {
                var minOptionChoice = this.parseTextFromClassName(component, "validation-minOptionChoice-");
                this.validationFailed(component, "Must have at least " + minOptionChoice + " choice(s) selected.");
            }
        }

        if(validations.indexOf("validation-maxOptionChoice") == 0 || validations.indexOf(" validation-maxOptionChoice") > 0) {
            if(!this.isMaxOptionChoice(value)) {
                var maxOptionChoice = this.parseTextFromClassName(component, "validation-maxOptionChoice");
                this.validationFailed(component, "Cannot have more than " + maxOptionChoice + " choice(s) selected.");
            }
        }
    },

    runValidationForDate: function(component, validations, label) {
        if(validations.indexOf("validation-teenager") != -1) {
            if(!this.isTeenager(component)) {
                this.validationFailed(component, "You must be at least 13 years old to use this site.");
            }
        }
    },

    isRequired: function(value) {
        //Value cannot be null
        return value != "" && value != null;
    },

    multipleChoiceIsRequired: function(component, element, radioGroup) {
        var atLeastOneIsSelected = false;
        if(element.type == "checkbox") {
            component.getElementsBySelector('input').each(function(input) {
                if(input.checked) {
                    atLeastOneIsSelected = input.checked;
                }
            });
        }
        else {
            atLeastOneIsSelected = this.getRadioButtonValue(element, radioGroup);
        }

        return atLeastOneIsSelected;
    },

    isBlank: function(value) {
        //Value must be null
        return value == "";
    },

    isUsername: function(value) {
        // Use 4 to 32 characters and start with a letter. You may use letters, numbers, underscores, and one dot (.).
        return value.match(/^[A-Za-z](?=[A-Za-z0-9_.]{3,31}$)[a-zA-Z0-9_]*\.?[a-zA-Z0-9_]*$/);
    },

    usernameIsAvailable: function(value, component) {
        var self = this;
        if(!this.isUsername(value) || value.blank()) {
            return true; // Lie to prevent doing an AJAX request
        }
        else {
            var url = "/php/classes/user.php";
            var params = {"task": "checkIfUsernameIsAvailable", "value": value};
            var ajaxRequest = new Ajax.Request(url, {
                parameters: params,
                onSuccess: function(transport) {
                    var json = transport.responseText.evalJSON(true);
                    if(json.success) {
                        usernameIsAvailable = true;
                    }
                    else {
                        self.validationFailed(component, "Username already in use.");
                        self.errorHandler(component, self.getErrors());
                    }

                }
            });
        }
    },
	
	 teamNameIsAvailable: function(value, component) {
			var self = this;
			if(!this.isUsername(value) || value.blank()) {
				return true; // Lie to prevent doing an AJAX request
			}
			else {
				var url = "/php/classes/user.php";
				var params = {"task": "checkIfTeamNameIsAvailable", "value": value};
				var ajaxRequest = new Ajax.Request(url, {
					parameters: params,
					onSuccess: function(transport) {
						var json = transport.responseText.evalJSON(true);
						if(json.success) {
							usernameIsAvailable = true;
						}
						else {
							self.validationFailed(component, "Username already in use.");
							self.errorHandler(component, self.getErrors());
						}
	
					}
				});
			}
		},	

    emailIsAvailable: function(value, component) {
        var self = this;
        if(!this.isEmail(value) || value.blank()) {
            return true; // Lie to prevent doing an AJAX request
        }
        else {
            var url = "/php/classes/user.php";
            var params = {"task": "checkIfEmailIsAvailable", "value": value};
            new Ajax.Request(url, {
                parameters: params,
                onSuccess: function(transport) {
                    var json = transport.responseText.evalJSON(true);
                    if(json.success) {
                        emailIsAvailable = true;
                    }
                    else {
                        self.validationFailed(component, "E-mail address already in use.");
                        self.errorHandler(component, self.getErrors());
                    }
                }
            });
        }
    },

    isPassword: function(value) {
        // Must be 4 or more characters and less than 33 characters
        return value.match(/^.{4,32}$/);
    },

    isTeenager: function(component) {
        var mm = $(component.id.replace("-div", "") + "-mm").getValue();
        var dd = $(component.id.replace("-div", "") + "-dd").getValue();
        var yyyy = $(component.id.replace("-div", "") + "-yyyy").getValue();
        var birthday = new Date(yyyy, mm - 1, dd);

        var now = new Date();
        var day = now.getDate();
        var month = now.getMonth();
        var year = now.getYear() + 1900 - 13;
        var limit = new Date(year, month, day);
        var timeDifference = (birthday - limit);

        return this.isNumericZeroNegative(timeDifference.toString());
    },

    getPasswordStrength: function(value) {
        var score = 0;
        if(value.length >= 6) {
            score = (score + 1) // at least six characters
        }
        if(value.length >= 10) {
            score = (score + 1) // 10 characters+ bonus
        }
        if(value.match(/[a-z]/)) { // [verified] at least one lower case letter
            score = (score + 1)
        }
        if(value.match(/[A-Z]/)) { // [verified] at least one upper case letter
            score = (score + 1)
        }
        if(value.match(/\d+/)) { // [verified] at least one number
            score = (score + 1)
        }
        if(value.match(/(\d.*\d)/)) { // [verified] at least two numbers
            score = (score + 1);
        }
        if(value.match(/[!,@#$%^&*?_~]/)) { // [verified] at least one special character
            score = (score + 1)
        }
        if(value.match(/([!,@#$%^&*?_~].*[!,@#$%^&*?_~])/)) { // [verified] at least two special characters
            score = (score + 1)
        }
        if(value.match(/[a-z]/) && value.match(/[A-Z]/)) { // [verified] both upper and lower case
            score = (score + 1)
        }
        if(value.match(/\d/) && value.match(/\D/)) { // [verified] both letters and numbers
            score = (score + 1)
        }
        if(value.match(/[a-z]/) && value.match(/[A-Z]/) && value.match(/\d/) && value.match(/[!,@#$%^&*?_~]/)) {
            score = (score + 1)
        }
        
        var strength = "None";
        
        if(score == 0) {
            strength = "None";
        }
        else if(score <= 1) {
            strength = "Very Weak";
        }
        else if(score <= 3) {
            strength = "Weak";
        }
        else if(score <= 5) {
            strength = "Good";
        }
        else if(score <= 7) {
            strength = "Strong";
        }        
        else if(score > 7) {
            strength = "Very Strong";
        }

        return {"score": score, "strength": strength};
    },

    matchesField: function(component, value) {
        var fieldToMatch = this.parseTextFromClassName(component, "validation-matches-");
        
        return value == $(fieldToMatch).getValue();
    },

    isNumeric: function(value) {
        //Can be negative and have a decimal value
        //Do not accept commas in value as the DB does not accept them
        if(value == "") {
            return true;
        };
        return value.match(/^-?((\d+(\.\d+)?)|(\.\d+))$/);
    },

    isNumericPositive: function(value) {
        //Must be positive and have a decimal value
        if(value == "") {
            return true;
        };
        var isNumeric = this.isNumeric(value);
        return isNumeric && parseFloat(value) > 0;
    },

    isNumericZeroPositive: function(value) {
        //Must be positive and have a decimal value
        if(value == "") {
            return true;
        };
        var isNumeric = this.isNumeric(value);
        return isNumeric && parseFloat(value) >= 0;
    },

    isNumericNegative: function(value) {
        //Must be negative and have a decimal value
        if(value == "") {
            return true;
        };
        var isNumeric = this.isNumeric(value);
        return isNumeric && parseFloat(value) < 0;
    },

    isNumericZeroNegative: function(value) {
        //Must be negative and have a decimal value
        if(value == "") {
            return true;
        };
        var isNumeric = this.isNumeric(value);
        return isNumeric && parseFloat(value) <= 0;
    },

    isInteger: function(value) {
        //Positive or negative whole number
        //Do not accept commas in value as the DB does not accept them
        if(value == "") {
            return true;
        };
        return value.match(/^-?\d+$/);
    },

    isIntegerPositive: function(value) {
        //Positive whole number
        if(value == "") {
            return true;
        };
        var isInteger = this.isInteger(value);
        return isInteger && parseInt(value) > 0;
    },

    isIntegerZeroPositive: function(value) {
        //Positive whole number
        if(value == "") {
            return true;
        };
        var isInteger = this.isInteger(value);
        return isInteger && parseInt(value) >= 0;
    },

    isIntegerNegative: function(value) {
        //Negative whole number
        if(value == "") {
            return true;
        };
        var isInteger = this.isInteger(value);
        return isInteger && parseInt(value) < 0;
    },

    isIntegerZeroNegative: function(value) {
        //Negative whole number
        if(value == "") {
            return true;
        };
        var isInteger = this.isInteger(value);
        return isInteger && parseInt(value) <= 0;
    },

    isAlpha: function(value) {
        //Only alpha characters and underscore
        if(value == "") {
            return true;
        };
        return value.match(/^[a-z_]+$/i);
    },

    isAlphanumeric: function(value) {
        //Match any alpha, number, or underscore characters
        if(value == "") {
            return true;
        };
        return value.match(/^\w+$/);
    },

    isDate: function(value) {
        //Match a date in mm/dd/yyyy format
        if(value == "") {
            return true;
        };
        return value.match(/^(0?[1-9]|1[012])[- \/.](0?[1-9]|[12][0-9]|3[01])[- \/.](19|20)?[0-9]{2}$/);
    },

    isTime: function(value) {
        //Match a date in hh:mm[:ss][ ][tt] format
        if(value == "") {
            return true;
        };
        return value.match(/^[0-2]?\d:[0-5]\d(:[0-5]\d)?( ?(a|p)m)?$/i);
    },

    isDateTime: function(value) {
        //Match a datetime in mm/dd/yyyy hh:mm[:ss][ ][tt] format
        if(value == "") {
            return true;
        };
        return value.match(/^(0?[1-9]|1[012])[- \/.](0?[1-9]|[12][0-9]|3[01])[- \/.](19|20)?[0-9]{2} [0-2]?\d:[0-5]\d(:[0-5]\d)?( ?(a|p)m)?$/i);
    },

    isUrl: function(value) {
        //Match a url
        if(value == "") {
            return true;
        };
        return value.match(/^(https?|ftp):\/\/([-A-Z0-9.]+)(\/[-A-Z0-9+&@#\/%=~_|!:,.;]*)?(\?[-A-Z0-9+&@#\/%=~_|!:,.;]*)?$/i);
    },

    isSsn: function(value) {
        //Match a ssn with or without hyphons
        if(value == "") {
            return true;
        };
        return value.match(/^\d{3}-?\d{2}-?\d{4}$/i);
    },

    isEmail: function(value) {
        //Match a email address
        if(value == "") {
            return true;
        };
        return value.match(/^[A-Z0-9._%-\+]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,4}$/i);
    },

    isZip: function(value) {
        //Match a US zip code
        if(value == "") {
            return true;
        };
        return value.match(/^[0-9]{5}(?:-[0-9]{4})?$/);
    },

    isCanadianPostal: function(value) {
        //Match a Canada postal code
        if(value == "") {
            return true;
        };
        return value.match(/^[ABCEGHJKLMNPRSTVXY][0-9][A-Z] [0-9][A-Z][0-9]$/);
    },

    isUkPostal: function(value) {
        //Match a UK postal code
        if(value == "") {
            return true;
        };
        return value.match(/^[A-Z]{1,2}[0-9][A-Z0-9]? [0-9][ABD-HJLNP-UW-Z]{2}$/);
    },

    isPostalZip: function(value) {
        //Match a us zip, canadian postal code, or uk postal code
        if(value == "") {
            return true;
        };
        if(this.isZip(value)) {
            return true;
        };
        if(this.isCanadianPostal(value)) {
            return true;
        };
        if(this.isUkPostal(value)) {
            return true;
        };

        return false;
    },

    isPhone: function(value) {
        //Match a North American Phone number with required area code
        if(value == "") {
            return true;
        };
        return value.match(/^(1[-. ]?)?\(?[0-9]{3}\)?[-. ]?[0-9]{3}[-. ]?[0-9]{4}$/);
    },

    isIsbn: function(value) {
        //Match an ISBN
        if(value == "") {
            return true;
        };
        //For ISBN-10
        if(value.match(/^(?=.{13}$)\d{1,5}([- ])\d{1,7}\1\d{1,6}\1(\d|X)$/)) {
            return true;
        };
        if(value.match(/^\d{9}(\d|X)$/)) {
            return true;
        };
        //For ISBN-13
        if(value.match(/^(?=.{17}$)\d{3}([- ])\d{1,5}\1\d{1,7}\1\d{1,6}\1(\d|X)$/)) {
            return true;
        };
        if(value.match(/^\d{3}[- ]\d{9}(\d|X)$/)) {
            return true;
        };
        //ISBN-13 without starting delimiter (Not a valid ISBN but less strict validation was requested)
        if(value.match(/^\d{12}(\d|X)$/)) {
            return true;
        };
        return false;
    },

    isMoney: function(value) {
        //Match a dollar value
        //Do not accept commas in value as the DB does not accept them
        if(value == "") {
            return true;
        };
        return value.match(/^((-?\$)|(\$-?)|(-))?((\d+(\.\d{2})?)|(\.\d{2}))$/);
    },

    isMoneyPositive: function(value) {
        //Match a dollar value
        if(value == "") {
            return true;
        };
        var isMoney = this.isMoney(value);
        return isMoney && parseFloat(value).replace("$", "") > 0;
    },

    isMoneyZeroPositive: function(value) {
        //Match a dollar value
        if(value == "") {
            return true;
        };
        var isMoney = this.isMoney(value);
        return isMoney && parseFloat(value).replace("$", "") >= 0;
    },

    isMoneyNegative: function(value) {
        //Match a dollar value
        if(value == "") {
            return true;
        };
        var isMoney = this.isMoney(value);
        return isMoney && parseFloat(value).replace("$", "") < 0;
    },

    isMoneyZeroNegative: function(value) {
        //Match a dollar value
        if(value == "") {
            return true;
        };
        var isMoney = this.isMoney(value);
        return isMoney && parseFloat(value).replace("$", "") <= 0;
    },

    isLength: function(value) {
        //Value must be the exact max length
        if(value == "") {
            return true;
        };
        return value.length == value.maxLength;
    },

    isMaxLength: function(value) {
        //Value must be the less than the max length
        if(value == "") {
            return true;
        };
        var maxLength = this.parseTextFromClassName(value, "maxLength-");
        if(maxLength == -1) maxLength = value.maxLength;
        if(!maxLength) return true;
        return value.length <= maxLength;
    },

    isMinLength: function(value) {
        //Value must be at least the min length
        if(value == "") {
            return true;
        };
        var minLength = this.parseTextFromClassName(value, "minLength-");
        if(minLength == -1) {
            return true;
        };
        return value.length >= minLength;
    },

    isMinOptionChoice: function(component) {
        //total number of selected choices must be more than the minimum
        var minOptionChoice = this.parseTextFromClassName(component, "validation-minOptionChoice-");
        if(minOptionChoice == -1) {
            return true;
        };
        var totalOptionSelected = 0;
        //TODO: does this work properly in IE 6?
        var sameOptionName = document.getElementsByName(component.getElementsBySelector('input')[0].name);
        for(var i=0;i<sameOptionName.length;i++) {
            if(sameOptionName[i].checked==true) {
                totalOptionSelected++;
            }
        }
        return totalOptionSelected >= minOptionChoice;
    },

    isMaxOptionChoice: function(value) {
        //total number of selected choices must be less than the maximum
        var maxOptionChoice = this.parseTextFromClassName(value, "maxOptionChoice-");
        if(maxOptionChoice == -1) {
            return true;
        };
        var totalOptionSelected = 0;
        //TODO: does this work properly in IE 6?
        var sameOptionName = document.getElementsByName(value.name);
        for(var i=0;i<sameOptionName.length;i++) {
            if(sameOptionName[i].checked==true) {
                totalOptionSelected++;
            }
        }
        return totalOptionSelected <= maxOptionChoice;
    },

    isMaxFloat: function(value) {
        //Value cannot have more digits then specified in maxFloat
        if(value == "") {
            return true;
        };
        var maxFloat = this.parseTextFromClassName(value, "maxFloat-");
        if(maxFloat == -1) {
            return true;
        };
        var maxFloatPattern = new RegExp("^-?((\\d+(\\.\\d{0,"+maxFloat+"})?)|(\\.\\d{0,"+maxFloat+"}))$");
        return value.match(maxFloatPattern);
    },

    parseTextFromClassName: function(element, className) {
        var classNames = element.classNames().toArray();
        var text = null;
        classNames.each(function(value) {
            if(value.indexOf(className) != -1) {
                text = value.replace(className, "");
            }
        });
 
        return text;
    },

    /**
     * Get the value of any group of radio buttons
     */
    getRadioButtonValue: function(element, radioGroup) {
        if($(element).type && $(element).type.toLowerCase() == 'radio') {
            var radioGroup = $(element).name;
            var element = $(element).form;
        }
        else if ($(element).tagName.toLowerCase() != 'form') {
            return false;
        }

        var checked = $(element).getInputs('radio', radioGroup).find(
            function(re) {
                return re.checked;
            }
        );

        return (checked) ? $F(checked) : false;
    }
});