// All fields to be valigated must be immediately in parent form tag

(function($) {

    // params is not supported yet
	$.fn.validatorInit = function(params, view)
	{
	    var tag = $(this);

		var sandbox = new ValidatorSandbox(tag);

		if (!view) {
			view = 'Default';
		}
		var view = eval('new ' + view + 'View(tag)');
		tag.data('view', view);
		tag.data('sandbox', sandbox);

		rules = new Array();
		tag.data('validatorRules', rules);

		return tag;
	};

	$.fn.validatorAddRule = function(classname, regexp, message)
	{
		var tag = $(this);
		var sandbox = tag.data('sandbox');
		sandbox.makeRule(classname, regexp, message);

		return tag;
	};

	$.fn.validatorRun = function()
	{
		var sandbox = $(this).data('sandbox');
		if (sandbox) {
            return sandbox.validateSandbox();
		} else {
		    return true;
		}
	};
	
	$.fn.validatorEnable = function()
	{
	    var sandbox = $(this).data('sandbox');
	    if (sandbox) {
	       sandbox.enable();
	    }
	};
	
	$.fn.validatorDisable = function()
	{
	    var sandbox = $(this).data('sandbox');
	    if (sandbox) {
            sandbox.disable();
	    }
	};
	
	// classes *********************************************************************

	with (ValidatorSandbox = function(tag) {
		this.tag = tag;

		var t = this;
		this.tag.find('.submit').bind('click', function(e){
			if (t.validatorStatus) {
    		    var result =  t.validateSandbox.call(t);
    			if (!result) {
    			    e.preventDefault();
    			    e.stopImmediatePropagation();
    			}
			}
		});
	}) {
		prototype.sandbox = null;
		prototype.validatorStatus = true;
		
		prototype.disable = function()
		{
		    this.validatorStatus = false;
		};
		
		prototype.enable = function()
        {
		    this.validatorStatus = true;
        };
		
		prototype.getRules = function()
		{
			var rules = this.tag.data('validatorRules');
			if (!rules) {
				rules = new Array();
			}
			return rules;
		};

		prototype.makeRule = function(classname, regexp, message)
		{
			var rules = this.getRules();
			var rule = new ValidatorRule(classname, regexp, message, this);
			rules.push(rule);
			this.tag.data('validatorRules', rules);

			this.tag.trigger('ruleadded.validator', [rule]);
		};

		prototype.getFieldsByClass = function(classname)
		{
			return this.tag.find('.' + classname);
		};

		prototype.validateSandbox = function()
		{
			var result = true;
			var rules = this.getRules();
			for (i in rules) {
				result =  rules[i].validateRule() && result;
			}

			return result;
		};
	}

	with (ValidatorRule = function(classname, regexp, message, sandbox) {
		this.classname = classname;
		this.regexp = regexp;
		this.message = message;
		this.sandbox = sandbox;
	}) {
		prototype.classname = null;
		prototype.params = null;
		prototype.message = null;
		prototype.sandbox = null;

		prototype.validateRule = function()
		{
			var result = true;
			var fields = this.getFields();

			var t = this;
			fields.each(function() {
				var field = $(this);
				field.trigger('fieldprevalidation.validator');

				var value = field.val();

				if (!t.regexp.test(value)) {
					result = false;
					t.sandbox.tag.trigger('rulefailed.validator', [t, field]);
				} else {
					t.sandbox.tag.trigger('rulesuccess.validator', [t, field]);
				}
			});

			return result;
		};

		prototype.getFields = function()
		{
			return this.sandbox.getFieldsByClass(this.classname);
		};
	}

	// views **************************************************************************

	with (DefaultView = function(tag){
		$().mousemove(function(e){
            mouseX = e.pageX;
            mouseY = e.pageY;
        });

        if ($('.validatorHint').length < 1) {
            var hint = $('<span class="validatorHint"></span>')
                .appendTo('body')
                .toggle(false);
        }

		tag
			.bind('ruleadded.validator', function(e, rule){
				var fields = rule.getFields();
				fields.each(function(){
					$(this)
						.bind('change keyup', function(){
							rule.validateRule();
						})
						.bind('blur', function(){
							$(this).data('focused', false);
						})
						.bind('focus', function(){
							$(this).data('focused', true);
						});
				});
			})
			.bind('rulefailed.validator', function(e, rule, field){
				var addHint = function()
				{
                    clearHint();
				    var offset = field.position();
				    var hint = $('<span></span>')
                        .attr('class', 'validatorFieldHint')
                        .css('position', 'absolute')
                        .css('top', offset.top)
                        .css('left', offset.left)
                        .css('z-index', 9999)
                        .text(rule.message)
                        .appendTo(field.parent())
                        .bind('click', function(){
                            field.focus();
                        });

                    applyFloatingHint(hint);

                    field.data('hintDescriptor', hint);
				};

				var clearHint = function()
				{
					var hint = field.data('hintDescriptor');
				    if (hint) {
				        hint.remove();
				    }
				};

				var applyFloatingHint = function(to)
				{
				    var floatingHint = $('.validatorHint');
				    to
    				    .bind('mousemove.view', function(e){
                            floatingHint
                                .css('top', mouseY + 10)
                                .css('left', mouseX + 10);
                        })
                        .bind('mouseover.view', function(){
                            floatingHint
                                .text(rule.message)
                                .toggle(true);
                        })
                        .bind('mouseout click blur keyup', function(){
                            floatingHint
                                .toggle(false);
                        });
				};

				field
					.addClass('validatorBadField')
					.unbind('.view')
					.bind('blur.view', function() {
						addHint();
					})
					.bind('focus.view click.view', function() {
						clearHint();
					});
				applyFloatingHint(field);

				if (!field.data('focused')) {
					addHint();
				}

			})
			.bind('rulesuccess.validator', function(e, rule, field) {
				field
					.removeClass('validatorBadField')
					.unbind('.view');
			});
	}) {
	}

	with (ContainerView = function(tag) {
	    this.tag = tag;
	    var t = this;
	    t.errors = {};
	    tag
	       .bind('rulefailed.validator', function(e, rule, field) {
	           t.errors[rule.classname] = rule.message;
	           t.displayErrors();
	       })
	       .bind('rulesuccess.validator', function(e, rule, field) {
               delete t.errors[rule.classname];
               t.displayErrors();
           });

	}) {
	    prototype.tag = null;
	    prototype.errors = {};

	    prototype.displayErrors = function()
	    {
	        var container = this.tag.find('.error').empty();

	        isErrors = false;
	        for (i in this.errors) {
                container.append('<p>' + this.errors[i] + '</p>');
                isErrors = true;
            }

	        if (isErrors) {
                container.show();
	        } else {
                container.empty().hide();
	        }
	    };
	}


})(jQuery);

