(function($) {
    /*
    Validation Singleton
    */
    var Validation = function() {
        
        var rules = {
            
            email : {
               check: function(value) {
                   
                   if(value)
                       return testPattern(value,"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])");
                   return true;
               },
               msg : "Enter a valid e-mail address."
            },
            creditcard : {
               check: function(value) {
                   
                   if(value)
                       return isValidCreditCard(value);
                   return true;
               },
               msg : "Enter a valid credit card number."
            },
            url : {

               check : function(value) {

                   if(value)
                       return testPattern(value,"^https?://(.+\.)+.{2,4}(/.*)?$");
                   return true;
               },
               msg : "Enter a valid URL."
            },
            number : {

               check : function(value) {

                   if(value)
                       return isNumber(value);
                   return true;
               },
               msg : "This field must be numeric."
            },
            expiry : {

               check : function(value) {

                   if(value)
                       return testPattern(value,"^[0-9]{1,2}[\/][0-9]{2,4}$");
                   return true;
               },
               msg : "Enter exactly as it appears on the card<br/>(e.g. 12/2010 or 05/12)."
            },
            required : {
                
               check: function(value) {

                   if(value)
                       return true;
                   else
                       return false;
               },
               msg : "This field is required."
            }
        }
        var testPattern = function(value, pattern) {

            var regExp = new RegExp(pattern,"");
            return regExp.test(value);
        }
        return {
            
            addRule : function(name, rule) {

                rules[name] = rule;
            },
            getRule : function(name) {

                return rules[name];
            }
        }
    }
    
    /* 
    Form factory 
    */
    var Form = function(form) {
        
        var fields = [];
    
        form.find("[validation]").each(function() {
            var field = $(this);
            if(field.attr('validation') !== undefined) {
                fields.push(new Field(field));
            }
        });
        this.fields = fields;
    }
    Form.prototype = {
        validate : function() {

            for(field in this.fields) {
                
                this.fields[field].validate();
            }
        },
        isValid : function() {
            
            for(field in this.fields) {

                if(!this.fields[field].valid) {
            
                    this.fields[field].field.focus();
                    return false;
                }
            }
            return true;
        }
    }
    
    /* 
    Field factory 
    */
    var Field = function(field) {

        this.field = field;
        this.valid = false;
        this.attach("change");
    }
    Field.prototype = {
        
        attach : function(event) {
        
            var obj = this;
            if(event == "change") {
                obj.field.bind("change",function() {
                    return obj.validate();
                });
            }
            if(event == "keyup") {
                obj.field.bind("keyup",function(e) {
                    return obj.validate();
                });
            }
        },
        validate : function() {
            
            var obj = this,
                field = obj.field,
                errorClass = "errorlist",
                errorlist = $(document.createElement("p")).addClass(errorClass),
                types = field.attr("validation").split(" "),
                container = field.parent(),
                errors = []; 
            
            field.next(".errorlist").remove();
            for (var type in types) {

                var rule = $.Validation.getRule(types[type]);
                if(!rule.check(field.val())) {

                    container.addClass("error");
                    errors.push(rule.msg);
                }
            }
            if(errors.length) {

                obj.field.unbind("keyup")
                obj.attach("keyup");
                field.after(errorlist.empty());
                for(error in errors) {              
                    errorlist.append(errors[error] +"<br/>");        
                }
                obj.valid = false;
            } 
            else {
                errorlist.remove();
                container.removeClass("error");
                obj.valid = true;
            }
        }
    }
    
    /*
    Validation extends jQuery prototype
    */
    $.extend($.fn, {
        
        validation : function() {
            
            var validator = new Form($(this));
            $.data($(this)[0], 'validator', validator);
            
            $(this).bind("submit", function(e) {
                validator.validate();
                if(!validator.isValid()) {
                    e.preventDefault();
                }
            });
        },
        validate : function() {
            
            var validator = $.data($(this)[0], 'validator');
            validator.validate();
            return validator.isValid();
            
        }
    });
    $.Validation = new Validation();
})(jQuery);


function isValidCreditCard(cardNumStr) {

    // Remove all dashes and spaces, in case the user put them in for readability
    cardNumStr = cardNumStr.replace(/[-\s]/g, "");
 
    // Make sure only digits are left (i.e. there aren't any weird chars)
    if (cardNumStr.match(/\D/g)) {
        return false;
    }
 
    // Make sure enough digits -- all major cards have numbers 14+ digits in length
    if (cardNumStr.length < 14) {
        return false;
    }
 
    // We must process the cardNumStr from right to left, so we flip it to make
    // that easier
    var revCardNumStr = cardNumStr.split("").reverse().join("");
 
    // We create a check string, which is derived from doubling every other
    // character in the revCardNumStr, and concatenating all (doubled or not),
    // the chars together
    var checkStr = "";
    for (var i = 0; i < revCardNumStr.length; i++) {
        // Double every second character
        if (i % 2 === 1) {
            checkStr += (parseInt(revCardNumStr.charAt(i), 10) * 2);
        } else {
            checkStr += parseInt(revCardNumStr.charAt(i), 10)
        }
    }
 
    // Now we sum the digits of the checkStr, and hope it's mod 10
    var checkSum = 0;
    for (var i = 0; i < checkStr.length; i++) {
        checkSum += parseInt(checkStr.charAt(i), 10);
    }


    return checkSum % 10 === 0;
};

function isNumber(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}


