;(function($) {
    $.fn.validate = function(url, options) {

        var opts = $.extend({}, $.fn.validate.defaults, options);
        
        return this.each(function() {
            var form = $(this).addClass('validated');
            
            function displayError(element, errors){
                getStatus(element).addClass('errorlist').removeClass('valid').find('li').text(errors[0]);                
                getWrapper(element).addClass('errors');
            }
            
            function displayOk(element){
                getStatus(element).addClass('valid').removeClass('errorlist').find('li').text('');
                getWrapper(element).removeClass('errors');
                element.trigger('valid');
            }
            
            function displayNone(element){
                getStatus(element).removeClass('errorlist').removeClass('valid').find('li').text('');
                getWrapper(element).removeClass('errors');
                element.trigger('valid');
            }
            
            function getStatus(element){
                var fieldWrapper = getWrapper(element);
                
                // find errorlist generated on server
                var errorlist = $('.errorlist', fieldWrapper);
                if (errorlist.length > 0) 
                    errorlist.addClass('validation');
                
                var status = $('.validation', fieldWrapper);
                if (status.length == 0) {
                    status = $('<ul class="validation"/>').append('<li/>');
                    if (fieldWrapper.hasClass('CheckboxInput')) {
                        status.insertAfter(element.siblings('label:eq(0)'));
                    } else if (fieldWrapper.hasClass('RadioSelect')) {
                        status.insertAfter(fieldWrapper.find('> ul'));
                    } else if (fieldWrapper.hasClass('GroupedRadioSelect')) {
                        $('label:eq(0)', fieldWrapper).after(status);
                    } else {
                        status.insertAfter(element);
                    }
                }
                
                return status;
            }
            
            function getWrapper(element) {
                return element.parents(opts.wrapper);
            }
            
            function validate(element, noLoading) {
                var data = form.serializeArray();
                data.push({name:'fields', value: element.attr('name')});
                
                var status = getStatus(element);
                
                if (!noLoading) {
                    status.addClass('loading').find('li').text('');
                }

                $.ajax({
                    type: "POST",
                    url: url,
                    data: data,
                    dataType: 'json',
                    timeout: 1000,
                    success: function(response){
                        status.removeClass('loading');
                     
                        var id = element.attr('id');
                    
                        if (response.valid) {
                            displayOk(element);                            
                        } else if (id in response.errors) {
                            displayError(element, response.errors[id]);
                        } else if (opts.displayOkIfNotInvalid) {
                            displayOk(element);
                        }
                    },
                    error: function(XMLHttpRequest, textStatus){
                        status.removeClass('loading');
                        displayNone(element);
                    }
                });
            }
            
            function validateAll(displayErrors, callback) {
                var data = form.serializeArray();
                $.post(url, data, function(response){
                    if (!response.valid && displayErrors) {
                        $.each(response.errors, function(id, errors){
                            displayError($('#' + id), errors)
                        });
                    }
                    if (callback)
                        callback(response);
                    opts.onValidateAll(response);
                }, 'json');
            }
            
            form.bind('validateAll', function(e, callback){
                validateAll(true, callback);  
            })
            
            $('input[type="text"], textarea, select', form)
                .change(function(){
                    validate($(this));
                    //validateAll();
                })
                .blur(function (){
                    if($(this).val() == '') {
                        if($(this).siblings().hasClass('valid')) {
                            validate($(this));
                        }
                    }
                });

            $(opts.wrapper + '.errors input[type="text"], ' +
              opts.wrapper + '.errors textarea')
                .live('keyup', function(){
                    validate($(this), true);    
                });
            $('input[type="checkbox"], input[type="radio"]').click(function(){
                validateAll();
            });

            $(opts.wrapper + '.errors input[type="checkbox"], '
            + opts.wrapper + '.errors input[type="radio"]').live('click', function(){
                validate($(this));
            });            
        })
    }
    
    $.fn.validate.defaults = {
        onValidateAll: function() {},
        wrapper: '.field_wrapper',
        displayOkIfNotInvalid: false
    }
    
})(jQuery);

