/* FORM VALIDATION
   There are two forms of validation in this script: a basic check that a field is filled out (for mandatory fields)
   and a more complex check against a specific pattern or the content of another field. The pattern used on Scarlett
   is as follows: include a validator initialization script in the template. Here's a sample one:
   
   $(document).ready(function() {
       checks = new Array(
           new CheckSet('contest_email',[new Check(PATTERN_EMAIL, 'Email address not properly formatted')]),
           new CheckSet('contest_phone',[new Check(/1?[\-\.]?\(?[0-9]{3}\)?\W*[\-\.]?\W*[0-9]{3}[\-\.]?[0-9]{4}/, 'Phone number not properly formatted (should be like 555-555-1212)')])
           );
       validator = new Validator('frm_email_form',checks);
       validator.validate();
   });
   
   Mandatory field validation is handled via the form markup: mark any required fields with a class of "mandatory"
   and describe the field in the title attribute (used for the error message).
   
   Pattern-based validation or validation against a second form field goes in the init script. Create a new CheckSet with the field name/ID, the array of pattern regexes
   (if any), the form field to check against (if any), and the error message if the check-against-field fails. The error message
   for the pattern-based check goes in the array of Check objects.
*/

PATTERN_EMAIL = /^[0-9A-Za-z_\+\.\-]+\@([0-9A-Za-z\-]+\.)+([A-Za-z0-9]{2,7})$/;
PATTERN_PCODE = /[ABCEGHJKLMNPRSTVXY][0-9][A-Z][ \-]?[0-9][A-Z][0-9]/i;

/* realTimeStaticValidate
   Validates input to a field on the fly, based on a predefined regex pattern.
   field: jQuery object. The form field to check.
   checks: Array of Check objects. The validation regexes to check against.
*/
function realTimeStaticValidate(field, checks) {
    field.bind('keyup change blur validate', function(evt) {
        var tests = new Array(checks.length);
        for(var i = 0; i < tests.length; i++) {
            if(($(this).val().search(checks[i].getRegex()) != -1 && checks[i].getMatchPattern()) || ($(this).val().search(checks[i].getRegex()) == -1 && !checks[i].getMatchPattern())) {
                tests[i] = '';
            }
            else { // no match when match required, or match when match forbidden
                tests[i] = checks[i].getError();
            }
        }
        if(tests.join('').length == 0) {
            if(evt.type == 'validate' && getFlag($(this)) && getFlag($(this)).attr('class').indexOf('invalid') != -1) {
                // don't wipe out invalid flags set by other checks; do nothing
            }
            else {
                setFlag($(this), 'valid');
            }
        }
        else { // no match when match required, or match when match forbidden
            setFlag($(this), 'invalid', tests);
        }
    });
}

/* realTimeDynamicValidate
   Validates input to a field on the fly, based on another field's value.
   field: jQuery object. The form field to check.
   matchField: jQuery object. The form field with the value to check against.
   error: String (optional). The error message to throw if validation fails.
   matchPattern: boolean (optional). Whether to allow input that matches the pattern (true) or block it.
   allowEmpty: boolean (optional). Whether to allow empty input as valid.
*/
function realTimeDynamicValidate(field, matchField, error, matchPattern, allowEmpty) {
    matchPattern = typeof matchPattern !== 'undefined' ? matchPattern : true;
    allowEmpty = typeof allowEmpty !== 'undefined' ? allowEmpty : false;
    field.bind('keyup change blur', function() {
        if(!allowEmpty && $(this).val().search(/^\s*$/) != -1) {
            resetFlag($(this));
        }
        else if((!allowEmpty && matchField.val().search(/^\s*$/) == -1) && ($(this).val().search("^" + matchField.val() + "$") != -1 && matchPattern) || ($(this).val().match("^" + matchField.val() + "$") == -1 && !matchPattern)) {
            setFlag($(this), 'valid');
        }
        else { // no match and matchPattern, or match and don't match
            setFlag($(this), 'invalid', error);
        }
    });
    matchField.bind('keyup change blur', function() {
        if(!allowEmpty && field.val().search(/^\s*$/) != -1) {
            resetFlag(field);
        }
        else if((!allowEmpty && $(this).val().search(/^\s*$/) == -1) && ($(this).val().search("^" + field.val() + "$") != -1 && matchPattern) || ($(this).val().match("^" + field.val() + "$") == -1 && !matchPattern)) {
            setFlag(field, 'valid');
        }
        else { // no match and matchPattern, or match and don't match
            setFlag(field, 'invalid', error);
        }
    });
}

/* ajaxAccountCheck
   Checks the input to a username or email address field against the server database to see if an account
   already exists for that user.
   field: jQuery object. The field to check.
   fieldType: String. The database to check against. Currently the following options are available:
              'username' - user names
              'email' - email addresses
   failExists: boolean (optional). Whether the check should fail if the input exists (default, true) or pass (false).
*/
function ajaxAccountCheck(field, fieldType, failExists) {
    failExists = typeof failExists === 'boolean' ? failExists : true;
    // clear old ajax indicators
    field.siblings('.account_check').remove();
    
    if(field.val().search(/^\s*$/) == -1) { // if field is not empty
        setFlag(field, 'loading', 'Checking user database...')
        if(fieldType == 'username') {
            $.get('/user.php?do=username_exists&username='+field.val(), function(data) {
                if(failExists && data == '0' || !failExists && data == '1') {
                    if(!getFlag(field) || getFlag(field).attr('class') != 'flag_invalid') {
                        setFlag(field, 'valid');
                    }
                }
                else if(failExists && data == '1' || !failExists && data == '0') {
                    if(failExists) {
                        setFlag(field, 'invalid', 'This username already exists. You may already have an active account through RedFlagDeals');
                        var attemptLogin = $(document.createElement('p')).addClass('account_check').html('This username already exists. If you already have a RedFlagDeals account, you can <a href="/user/login/">use this login on Scarlett Lounge.</a>').css('display','none');
                    }
                    else {
                        setFlag(field, 'invalid', 'This username does not exist');
                        var attemptLogin = $(document.createElement('p')).addClass('account_check').html('This username does not exist.').css('display','none');
                    }
                    field.next().after(attemptLogin.fadeIn(500));
                }
            });
        }
        else if(fieldType == 'new_email') {
            $.get('/user.php?do=email_exists&email='+field.val(), function(data) {
                if(failExists && data == '0' || !failExists && data == '1') {
                    if(!getFlag(field) || getFlag(field).attr('class') != 'flag_invalid') {
                        setFlag(field, 'valid');
                    }
                }
                else if(failExists && data == '1' || !failExists && data == '0') {
                    if(failExists) {
                        setFlag(field, 'invalid', 'This email address is already associated with an account. You may already have an active account through RedFlagDeals');
                        var attemptLogin = $(document.createElement('p')).addClass('account_check').html('This email address is already associated with an account. If you already have a RedFlagDeals account, you can <a href="/user/login/">use this login on Scarlett Lounge.</a>').css('display','none');
                    }
                    else {
                        setFlag(field, 'invalid', 'This email address is not in our user database');
                        var attemptLogin = $(document.createElement('p')).addClass('account_check').html('This email address is not in our user database.').css('display','none');
                    }
                    field.next().after(attemptLogin.fadeIn(500));
                }
            });
        }
        else if(fieldType == 'change_email') {
            $.get('/user.php?do=email_exists&email='+field.val(), function(data) {
                if(failExists && data == '0' || !failExists && data == '1') {
                    if(!getFlag(field) || getFlag(field).attr('class') != 'flag_invalid') {
                        setFlag(field, 'valid');
                    }
                }
                else if(failExists && data == '1' || !failExists && data == '0') {
                    if(failExists) {
                        setFlag(field, 'invalid', 'This email address is already associated with an account');
                        var attemptLogin = $(document.createElement('p')).addClass('account_check').html('This email address is already associated with an account.').css('display','none');
                    }
                    else {
                        setFlag(field, 'invalid', 'This email address is not in our user database');
                        var attemptLogin = $(document.createElement('p')).addClass('account_check').html('This email address is not in our user database.').css('display','none');
                    }
                    field.next().after(attemptLogin.fadeIn(500));
                }
            });
        }
    }
    else {
        resetFlag(field);
    }
}

/* setFlag
   Creates the DOM element(s) necessary to display the validation status of a field.
   field: jQuery object. The form field to show a validation flag for.
   flagType: String. Sets the type of flag to show. Current options:
             'valid': valid input.
             'invalid': invalid input.
             'caution': Currently unused but would be used to indicate potentially invalid input.
             'loading': Loading indicator. Used during ajax queries until a proper flag can be shown.
   errors: String or Array of Strings. The error message to print.
*/
function setFlag(field, flagType, errors) {
    // create error message text
    if(typeof errors !== 'undefined') {
        if(errors.constructor == Array) {
            errorMsg = errors.join(', ').replace(', ,',',').replace(/^, /, '').replace(/(, )?$/,'.');
        }
        else {
            errorMsg = errors + '.';
        }
    }
    else {
        errorMsg = '';
    }
    
    // set background colour
    if(field.parents('.section_pink').length > 0) {
        colour = 'pink';
    }
    else {
        colour = 'white';
    }
    if(field.next().length != 0 && field.next().attr('class').indexOf('flag') != -1) {
        field.next().attr('src','/images/indicators/'+flagType+'_'+colour+'.gif').attr('class','flag_'+flagType).attr('title',errorMsg);
    }
    else {
        field.after($(document.createElement('img')).attr('src','/images/indicators/'+flagType+'_'+colour+'.gif').attr('class','flag_'+flagType).attr('title',errorMsg));
    }
}

/* resetFlag
   Removes any existing flag from a field.
   field: jQuery object. The form field to reset a validation flag for.
*/
function resetFlag(field) {
    if(field.next().length != 0 && field.next().attr('class').indexOf('flag') != -1) {
        field.next().remove();
    }
}

/* getFlag
   Get the validation flag for a field, if any.
   field: jQuery object. The form field to get a validation flag for.
*/
function getFlag(field) {
    if(field.next().length != 0 && field.next().attr('class').indexOf('flag') != -1) {
        return field.next();
    }
    else {
        return false;
    }
}

/* checkMandatory
   Checks to see that mandatory fields in a form have been filled. Relies on presence
   of form fields marked with the "mandatory" class.
   Does not validate input beyond checking to see if input is available; that job
   should be performed by field-specific validators.
   form: jQuery object. The form whose fields should be checked for non-null input.
*/
function checkMandatory(form) {
    var fields = form.find('.mandatory');
    // remove old "field missing" markers
    fields.each(function() {
        $(this).removeClass('missing');
    });

    // create Array to store all name attributes of missing fields
    var missingFields = new Array();

    // check for empty text fields
    fields.filter(':text,:password,textarea').each(function() {
        if($(this).val().search(/^\s*$/) != -1) {
            $(this).addClass('missing');
            missingFields.push($(this).attr('name'));
        }
    });
    
    // check for unselected radio groups
    var radioGroups = new Array();
    fields.filter(':radio').each(function() {
        nodename = $(this).attr('name');
        radioGroups.push(nodename); // push radio group onto stack
        for(var i = 0; i < radioGroups.length - 1; i++) { // check all items on stack except the group just added
            if(radioGroups[i] == nodename) {
                radioGroups.pop(); // if duplicate found, remove the group just added
                break;
            }
        }
    });
    for(var i = 0; i < radioGroups.length; i++) {
        if(fields.filter(':radio:checked[name='+radioGroups[i]+']').length < 1) {
            fields.filter(':radio[name='+radioGroups[i]+']').addClass('missing');
            missingFields.push(radioGroups[i]);
        }
    }
    
    // check for unchecked boxes
    fields.filter(':checkbox').each(function() {
        if(!$(this).attr('checked')) {
            $(this).addClass('missing');
            missingFields.push($(this).attr('name'));
        }
    });
    
    // check for selects with blank values
    fields.filter('select').each(function() {
        if($(this).val().search(/^\s*$/) != -1) {
            $(this).addClass('missing');
            missingFields.push($(this).attr('name'));
        }
    });
    
    return missingFields;
}

/* checkFlags
   Checks to see that no validation errors are present in any form fields.
   form: jQuery object. The form whose fields should be checked for valid flags.
*/
function checkFlags(form) {
    var errorFields = new Array();

    // check all fields, even ones that haven't been changed (ex. birth year, which is already filled out at start)
    $.event.trigger('validate');
    
    // check for presence of invalid flags and collect errors
    form.find('.flag_invalid').each(function() {
        errorFields.push($(this).attr('title'));
    })
    return errorFields;
}

// class Check
// A three-element structure that holds a validation regex and an error message.
// regex: RegExp object. The validation regex.
// error: String (optional). The error message to throw if validation fails.
// matchPattern: boolean (optional). Whether to allow input that matches the pattern (true) or block it.
function Check(regex, error, matchPattern) {
    this.regex = regex;
    this.error = typeof error == 'string' ? error : null;
    this.matchPattern = typeof matchPattern !== 'undefined' ? matchPattern : true;
    
    this.getRegex = function() {
        return this.regex;
    }
    this.getError = function() {
        return this.error;
    }
    this.getMatchPattern = function() {
        return this.matchPattern;
    }
    this.toString = function() {
        return this.regex.toString();
    }
    this.isCheck = function() {
        return true;
    }
}

// class CheckSet
// An object containing a set of validation regexes and elements to be used for validating a single field.
// field: String. The ID of the field to validate.
// checks: Array of Check objects (optional). The validation regexes.
// matchField: String (optional). The ID of the field to match on.
// matchError: String (optional). The error message to throw if contents of matchField don't match.
function CheckSet(field, checks, matchField, matchError) {
    this.field = field;
    this.matchField = typeof matchField !== 'undefined' ? matchField : null;
    this.matchError = typeof matchError !== 'undefined' ? matchError : null;
    if(checks && typeof checks !== 'undefined' && checks.constructor == Array && checks[0].isCheck()) {
        this.checks = checks;
    }
    else {
        this.checks = new Array();
        if(checks && checks.isCheck()) {
            this.checks[0] = checks;
        }
    }
    
    this.addCheck = function(check) {
        for(var i = 0; i < this.checks.length - 1; i++) {
            if(this.checks[i].toString() == check.toString()) {
                return false;
            }
        }
        this.checks.push(check);
        return true;
    };
    this.addChecks = function(checks) {
        for(var i = 0; i < checks.length; i++) {
            this.addcheck(checks[i]);
        }
    }
    this.getFieldID = function() {
        return this.field;
    };
    this.getField = function() {
        return $('#'+this.field);
    };
    this.getMatchID = function() {
        return this.matchField;
    };
    this.getMatchField = function() {
        return $('#'+this.matchField);
    };
    this.getMatchError = function() {
        return this.matchError;
    };
    this.getChecks = function() {
        return this.checks;
    };
    this.isCheckSet = function() {
        return true;
    }
}

// class Validator
function Validator(form, checkSets) {
    this.form = form;
    if(typeof checkSets !== 'undefined' && checkSets.constructor == Array && checkSets[0].isCheckSet()) {
        this.checkSets = checkSets;
    }
    else {
        this.checkSets = new Array();
        if(checkSets.isCheckSet()) {
            this.checkSets[0] = checkSets;
        }
    }
    
    this.addCheckSet = function(checkSet) {
        var field = checkSet.getFieldID();
        this.checkSets.push(checkSet);
        for(var i = 0; i < this.checkSets.length - 1; i++) {
            if(this.checkSets[i].getFieldID() == field) {
                this.checkSets.pop();
                this.checkSets[i].addChecks(checkSet.getChecks());
            }
        }
    };
    this.getForm = function() {
        return $('#'+this.form);
    };
    this.validate = function() {
        this.getForm().children().eq(0).before($(document.createElement('a')).attr('name',this.form + '_top'));
        for(var i = 0; i < this.checkSets.length; i++) {
            var currentField = this.checkSets[i].getField();
            var currentChecks = this.checkSets[i].getChecks();
            if(this.getForm().find('#' + this.checkSets[i].getFieldID()).length > 0) { // if currentField is actually in form, check it
                //console.log('woot');
                if(currentChecks.length > 0) {
                    realTimeStaticValidate(currentField, currentChecks);
                }
                if(this.checkSets[i].getMatchID()) {
                    realTimeDynamicValidate(currentField, this.checkSets[i].getMatchField(), this.checkSets[i].getMatchError());
                }
            }
        }
        this.getForm().submit(function() {
            $('.error').remove();
            missing = checkMandatory($(this));
            errors = checkFlags($(this));
            var validForm = missing.length == 0 && errors.length == 0;
            if(!validForm) {
                errorBox = $(document.createElement('div')).addClass('error').html('<h4>There are errors in this form.</h4>');
                if(missing.length > 0) {
                    errorBox.append($(document.createElement('p')).text('The following fields are missing (marked as yellow fields):'));
                    missingList = $(document.createElement('ul'));
                    for(var i = 0; i < missing.length; i++) {
                        missingList.append($(document.createElement('li')).text($(this).find('[name='+missing[i]+']').attr('title')));
                    }
                    errorBox.append(missingList);
                }
                if(errors.length > 0) {
                    errorBox.append($(document.createElement('p')).text('The following fields have errors (marked with red X\'s):'));
                    errorList = $(document.createElement('ul'));
                    for(var i = 0; i < errors.length; i++) {
                        errorList.append($(document.createElement('li')).text(errors[i]));
                    }
                    errorBox.append(errorList);
                }
                errorBox.append($(document.createElement('p')).text('Please go back and correct these errors before submitting.'));
                $(this).prepend(errorBox);
                $(document).scrollTop(errorBox.offset().top);
            }
            return validForm;
        });
    };
}