var minPasswordLength = 4;
var maxPasswordLength = 40;


function isEmail(email)
{
    var emailRegEx = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
    return null != email.match( emailRegEx );    
}

function isUrl(url)
{
	return null != url.match(/^(ht|f)tps?:\/\/[a-z0-9-\.]+\.[a-z]{2,4}\/?([^\s<>\#%"\,\{\}\\|\\\^\[\]`]+)?$/);
}

function isZipCode(zip)
{
    return null != zip && null != zip.match( /^\d{5}$|^\d{5}-\d{4}$/ );    
}

function isPhoneNumber(phone)
{
    return null != phone && null != phone.match( /^\d\d\d-?\d\d\d-?\d\d\d\d$/ );
}

function isPositiveInteger( str )
{
    return null != str && null != str.match( /^[0-9]+$/ );
}

// Takes in a given url and returns a version prefixed with http://
function httpify(url)
{
    if( "http://" != url.slice(0,7).toLowerCase() )
        url = "http://" + url;
    return url;
}

// Uses the luhn algorithm to do a basic credit card number validation
function luhn_check(number)
{
  // Strip any non-digits (useful for credit card numbers with spaces and hyphens)
  var number=number.replace(/\D/g, '');

  // Set the string length and parity
  var number_length=number.length;
  var parity=number_length % 2;

  // Loop through each digit and do the maths
  var total=0;
  for (i=0; i < number_length; i++) {
    var digit=number.charAt(i);
    // Multiply alternate digits by two
    if (i % 2 == parity) {
      digit=digit * 2;
      // If the sum is two digits, add them together (in effect)
      if (digit > 9) {
        digit=digit - 9;
      }
    }
    // Total up the digits
    total = total + parseInt(digit);
  }

  // If the total mod 10 equals 0, the number is valid
  return total % 10 == 0;
}

// Computes a unique id given a formId and an input element id
function computeDynamicSpanId( formId, id )
{
    return formId + "_" + id + "_message";
}

// Displays or hides a dynamic message on the input element that corresponds to id
function showDynamicMessage( formId, id, clss, show, message )
{
    var spanId = computeDynamicSpanId( formId, id );
    if( 0 == $("#" + spanId).size() )
        $("#" + formId + " #" + id ).after( "<span id='" + spanId + "'></span>");

    var hideAndFadeIn = !$("#" + spanId).hasClass( clss );
    $("#" + spanId).removeClass().addClass( 'dynamic_validation_message' ).addClass( clss ).html( message );
    if( hideAndFadeIn )
        $("#" + spanId).hide().fadeIn(840, function() { /*$(this).css("display", "inline")*/ });
}

// Given a form id and an input element id checks if the specified input element currently has a dynamic validation message
function hasDynamicValidationMessage( formId, id)
{
    var spanId = computeDynamicSpanId( formId, id );
    return  $("#" + spanId).size() > 0;
}

// Given a form id and an input element id checks if the specified input element currently has a dynamic validation error message
function hasDynamicErrorMessage( formId, id )
{
    var spanId = computeDynamicSpanId( formId, id );
    return $("#" + spanId).size() > 0 && $("#" + spanId).hasClass('error');   
}

// Binds all the dynamic validation event handlers to the input elements of a given form
function bindDynamicValidators( formId )
{
    var errorInfo = {value: 0, message: ''}
    $("#" + formId + " :input" ).each(  function(){
            var inputType = $(this).attr('type');
            if( "text" == inputType || "textarea" == inputType )
            {
                //lost focus event handler
                $(this).blur(function(){
                    //check if input is correct and apply the appropriate message
                    if( validateInput( formId, $(this).attr('id'), false, errorInfo ) )
                        showDynamicMessage( formId, $(this).attr('id'), "validate_success", true, "<em>good</em>"  );
                    else
                        showDynamicMessage( formId, $(this).attr('id'), "validate_error", true, errorInfo.message );
                });

                //keyup event handler
                $(this).keyup(function(){
                    //only update dynamic validation on keyup when there is already a message present
                    if( hasDynamicValidationMessage( formId, $(this).attr('id') ) )
                    {
                        //check if input is correct and apply the appropriate message
                        if( validateInput( formId, $(this).attr('id'), false, errorInfo ) )
                            showDynamicMessage( formId, $(this).attr('id'), "validate_success", true, "<em>good</em>"  );
                        else
                            showDynamicMessage( formId, $(this).attr('id'), "validate_error", true, errorInfo.message );
                    }
                });
            }});
}

function applyError( formId, id, applyErrorsToForm, errorCountOut, message )
{
    errorCountOut.value++;
    errorCountOut.message = message;
    if(applyErrorsToForm)
        applyJSONErrorToForm( id, message, formId, true, false );
    return false;
}

function applyExternalError( formId, id, confirmationId, classToFind, applyErrorsToForm, errorCountOut, message )
{
    errorCountOut.value++;

    if(applyErrorsToForm)
    {
        applyJSONErrorToForm( id, "", formId, false, false );
        $("#" + formId).find( '.' + classToFind ).html(message).removeClass('hidden').hide().fadeIn(300);
        $("#" + formId + " #" + id ).click( function(){$("#" + formId).find("." + classToFind ).fadeOut(300); $('#' + confirmationId).removeClass('error');});
        if( confirmationId.length > 0 )
            $("#" + formId + " #" + confirmationId ).click( function(){$("#" + formId).find("." + classToFind).fadeOut(300); $('#' + id).removeClass('error'); });
    }

    return false;
}

function applyPasswordError( formId, id, confirmationId, applyErrorsToForm, errorCountOut, message )
{
    return applyExternalError( formId, id, confirmationId, 'password_error', applyErrorsToForm, errorCountOut, message);
}

function applyEmailError( formId, id, confirmationId, applyErrorsToForm, errorCountOut, message )
{
    return applyExternalError( formId, id, confirmationId, 'email_error', applyErrorsToForm, errorCountOut, message);     
}

// Validates a form and an input element
function validateInput( formId, id, applyErrorsToForm, errorCountOut )
{
    var errorCount = {value: 0, message: ''};

    if( null != id && id.length > 0 )
    {
    $("#" + formId + " #" + id ).each( function(){
        var value = $(this).attr('value').trim();
        var isEmptyValue = (null == value || 0 == value.trim().length);

        if($(this).hasClass('error'))
        {
            errorCount.value++;
            return false;
        }
        else
        if($(this).hasClass('default_text'))
            return applyError( formId, id, applyErrorsToForm, errorCount, "can't be blank" );
        else
        {
            if( $(this).hasClass('validates_presence_of') && isEmptyValue )
                return applyError( formId, id, applyErrorsToForm, errorCount, "can't be blank" );
    
            if($(this).hasClass('validates_as_integer'))
            {
                if( isEmptyValue )
                {
                     if( !$(this).hasClass('allow_empty') )
                        return applyError( formId, id, applyErrorsToForm, errorCount, "must be number" );
                }
                else
                {
                    if( !isPositiveInteger( value ) )
                        return applyError( formId, id, applyErrorsToForm, errorCount, "must be number" );
                }
            }

            if( $(this).hasClass('validates_as_password') )
            {
                var password_confirmation = $( '#' + id + '_confirmation' );
                var confirmationId = password_confirmation.size() > 0 ? password_confirmation.attr('id') : '';

                if( isEmptyValue )
                    return applyPasswordError( formId, id, confirmationId, applyErrorsToForm, errorCount, 'Please enter a password.' );
                else if( value.length < minPasswordLength )
                    return applyPasswordError( formId, id, confirmationId, applyErrorsToForm, errorCount, 'Please enter a password that is at least ' + minPasswordLength + ' characters long.'  );
                else if( value.length > maxPasswordLength )
                    return applyPasswordError( formId, id, confirmationId, applyErrorsToForm, errorCount, 'Please enter a password that is less than ' + maxPasswordLength + ' characters.'  );
                else
                {
                    if( password_confirmation.size() > 0 && password_confirmation.val() != value )
                        return applyPasswordError( formId, id, confirmationId, applyErrorsToForm, errorCount, 'Password and password confirmation do not match.' );
                }
            }

            if( $(this).hasClass('validates_as_checked'))
            {
                if( !$(this).is(':checked') )
                {
                     errorCount.value++;
                    if(applyErrorsToForm) applyJSONErrorToForm( id, "must be checked", formId, true, false );
                    $(this).parent().find( ".checkbox_error" ).removeClass('hidden').hide().fadeIn(300);
                    $(this).click( function(){$(this).parent().find(".checkbox_error").fadeOut(300); });
                    return false;
                }
            }

            if($(this).hasClass('validates_as_selected'))
            {
                if( (null == value) || (isEmptyValue) || (-1 != value.toLowerCase().indexOf('select')) )
                     return applyError( formId, id, applyErrorsToForm, errorCount, "must be selected" );
            }

            if($(this).hasClass('validates_as_email'))
            {
                var email_confirmation = $( '#' + id + '_confirmation' );
                var emailConfirmationId = email_confirmation.size() > 0 ? email_confirmation.attr('id') : '';

                if( !isEmail( value ) )
                     return applyError( formId, id, applyErrorsToForm, errorCount, "invalid email" );
                else if( email_confirmation.size() > 0 && email_confirmation.val() != value )
                    return applyEmailError( formId, id, emailConfirmationId, applyErrorsToForm, errorCount, 'Email and email confirmation do not match.' );
            }

            if($(this).hasClass('validates_as_url'))
            {
                 if( !isUrl( value ) )
                 {
                    var httpified_url = httpify(value);
                    if( isUrl(httpified_url) )
                        $(this).attr("value", httpified_url);
                    else
                         return applyError( formId, id, applyErrorsToForm, errorCount, "Web address is invalid" );
                 }
            }

            if($(this).hasClass('validates_as_zipcode') && !isZipCode( value ) )
                return applyError( formId, id, applyErrorsToForm, errorCount, "invalid zipcode" );


            if($(this).hasClass('validates_as_phone') && !isPhoneNumber( value ) )
                return applyError( formId, id, applyErrorsToForm, errorCount, "invalid phone number" );

            if($(this).hasClass('validates_as_credit_card_number') && (!isPositiveInteger( value ) || !luhn_check( value )) )
                 return applyError( formId, id, applyErrorsToForm, errorCount, "invalid credit card number" );

            if($(this).hasClass('validates_as_credit_card_verification_code'))
            if( !isPositiveInteger( value ) || value.length < 3 || value.length > 4 )
                return applyError( formId, id, applyErrorsToForm, errorCount, "invalid verification code" );

            if( 'file' == $(this).attr('type') )
            {
                if( 0 == value )
                {
                   if(applyErrorsToForm) applyJSONErrorToForm( id, "You must enter a file.", formId, true, false);
                   $(this).change(function(){$(this).next().remove();});
                   errorCount.value++;
                   return true;                       
                }
            }
        }
    });
    }

    if( undefined !== errorCountOut )
    {
        errorCountOut.value += errorCount.value;
        errorCountOut.message = errorCount.message;   
    }
    return 0 == errorCount.value;
}

// Validates the specified form and populates any invalid input with errors.
// If validation is successful, this will return true
function validateForm( formId )
{
    $( '#' + formId + ' .dynamic_validation_message' ).remove();
    var numErrors = {value: 0}
    $("#" + formId + " :input").each(function(){validateInput( formId, $(this).attr('id'), true, numErrors );});
    return 0 == numErrors.value;
}

// Clears all the input elements on the specified form
function clearForm( formId, callAjaxFormClear )
{
    if( undefined === callAjaxFormClear )
        callAjaxFormClear = true;

    if( $( '#' + formId ).size() > 0 )
    {
        // if there is a possibility that ajax_form_utils is being used on this form, ajax_form_clear it
        if( callAjaxFormClear && undefined !== ajax_form_clear )
            ajax_form_clear( formId );

        // remove any file input validation messages
        $( '#' + formId + ' #file_input' ).remove();

        // call the native browser's implementation of form reset to clear out the actual values of forms
        $( '#' + formId )[0].reset();

        // remove any additional special classes and trigger the out of focus event on each input in order
        // to get rid of any focus based input styles
        $( "#" + formId + " :text, #" + formId + " :password, #" + formId + " :file" ).each( function(){
            $(this).removeClass('error').removeClass('success').trigger('blur');
        });

         // remove any dynamic validation messages
        $( '#' + formId + ' .dynamic_validation_message' ).remove();
    }
}

function convertTextBoxes( formId, from, to )
{
     $("#" + formId + " :text").each(  function(){
       if( from.toLowerCase() == $(this).attr('value').toLowerCase() )
       {
            $(this).attr('value', to);    
       }
     });
}

