Object.extend(Tapestry.ErrorPopup.prototype, {
    BUBBLE_VERT_OFFSET : -13,
    BUBBLE_HORIZONTAL_OFFSET: 244,
    BUBBLE_HEIGHT: "auto",
    initialize : function(field) {
		this.field = $(field);
		var top = new Element("div", {
			'class':	't-error-popup-top'
		});
		var topRight = $(new Element("div", {
			'class':	't-error-popup-topright'
		})).update(top);
		var topLeft = new Element("div", {
			'class':	't-error-popup-topleft'
		}).update(topRight);
		var bottom = new Element("div", {
			'class':	't-error-popup-bottom'
		});
		var bottomRight = $(new Element("div", {
			'class':	't-error-popup-bottomright'
		})).update(bottom);
		var bottomLeft = new Element("div", {
			'class':	't-error-popup-bottomleft'
		}).update(bottomRight);
		this.innerSpan = $(new Element("div", {
			'class':	't-error-popup-content'
		}));
		var contentRight = $(new Element("div", {
			'class':	't-error-popup-right'
		})).update(this.innerSpan);
		var contentLeft = new Element("div", {
			'class':	't-error-popup-left'
		}).update(contentRight);
		this.outerDiv = $(new Element("div", {
			'id' : this.field.id + ":errorpopup",
			'class' : 't-error-popup'
		})).update(contentLeft).insert({top:topLeft, bottom:bottomLeft}).hide();
		var body = $(document.body);
	    body.insert({
	        bottom: this.outerDiv
	    });
	    this.outerDiv.absolutize();
		this.outerDiv.observe("click", function(event) {
	        this.ignoreNextFocus = true;
	        this.stopAnimation();
	        this.field.activate();
	        Event.stop(event);  // Should be domevent.stop(), but that fails under IE
	    }.bindAsEventListener(this));
	    this.queue = {
	        position: 'end',
	        scope: this.field.id
	    };
	    Event.observe(window, "resize", this.repositionBubble.bind(this));
	    document.observe(Tapestry.FOCUS_CHANGE_EVENT, function(event) {
	        if (this.ignoreNextFocus) {
	            this.ignoreNextFocus = false;
	            return;
	        }
	        if (event.memo == this.field) {
	            this.fadeIn();
	            return;
	        }
	        // If this field is not the focus field after a focus change, then it's bubble,
	        // if visible, should fade out. This covers tabbing from one form to another. 
	        this.fadeOut();
	    }.bind(this));
    },
    fadeIn : function() {
        if (! this.hasMessage) return;
        this.repositionBubble();
        if (this.animation) return;
        this.animation = new Effect.Appear(this.outerDiv, {
            queue: this.queue,
            duration: 0.05,
            afterFinish: function()
            {
                this.animation = null;

                if (this.field != Tapestry.currentFocusField)
                    this.fadeOut();
            }.bind(this)
        });
    },
    fadeOut : function () {
        if (this.animation) return;

        this.animation = new Effect.Fade(this.outerDiv, {
            queue : this.queue,
            duration: 0.05,
            afterFinish: function()
            {
                this.animation = null;
            }.bind(this)
        });
    },
    showMessage : function() {
        this.stopAnimation();
        this.fadeIn();
    },
    buildMessage : function(message) {
        this.innerSpan.update(message);
        this.hasMessage = true;
    }
});

Object.extend(Tapestry.FieldEventManager.prototype, {
    initialize : function(field) {
        this.field = $(field);
        var id = this.field.id;
        this.label = $(id + '-label');
        this.icon = $(id + '-icon');
        this.translator = Prototype.K;
        document.observe(Tapestry.FOCUS_CHANGE_EVENT, function(event) {
            // If changing focus *within the same form* then
            // perform validation.  Note that Tapestry.currentFocusField does not change
            // until after the FOCUS_CHANGE_EVENT notification.
            if (Tapestry.currentFocusField == this.field &&
                this.field.form == event.memo.form) {
                this.validateInput();
            }
        }.bindAsEventListener(this));
        this.field.observe("focus", function(evt) {
        	var error = $T(this.field).validationError;
        	if(error) {
        		if (this.errorPopup == undefined) {
        			this.errorPopup = new Tapestry.ErrorPopup(this.field);
        		}
        		this.errorPopup.showMessage();
        	}
        }.bindAsEventListener(this));
	},
	showValidDecoration : function() {
		this.field.removeDecorations();
        if (this.icon) {
        	this.icon.removeClassName("t-error-icon");
        	this.icon.addClassName("t-valid-icon");
            if (! this.icon.visible()) {
                new Effect.Appear(this.icon);
            }
        }
	},
    /** Removes validation decorations if present. Hides the ErrorPopup,
     *  if it exists.
     */
    removeDecorations : function() {
        this.field.removeClassName("t-error");
        if (this.label) { this.label.removeClassName("t-error"); }
        if (this.icon) { this.icon.hide(); }
        if (this.errorPopup) { this.errorPopup.hide(); }
    },
    /**
     * Show a validation error message, which will add decorations to the
     * field and it label, make the icon visible, and raise the
     * field's Tapestry.ErrorPopup to show the message.
     * @param message validation message to display
     */
    showValidationMessage : function(message) {
        $T(this.field).validationError = true;
        $T(this.field.form).validationError = true;
        this.field.addClassName("t-error");
        if (this.label) {
            this.label.addClassName("t-error");
        }
        if (this.icon) {
        	this.icon.addClassName("t-error-icon");
        	this.icon.removeClassName("t-valid-icon");
            if (! this.icon.visible()) {
                new Effect.Appear(this.icon);
            }
        }
        if (this.errorPopup == undefined) {
            this.errorPopup = new Tapestry.ErrorPopup(this.field);
        }
        // build the error message popup, but don't show it just yet
        this.errorPopup.buildMessage(message);
    },
    /**
     * Invoked when a form is submitted, or when leaving a field, to perform
     * field validations. Field validations are skipped for disabled fields.
     *
     * @return true if the field has a validation error
     */
    validateInput : function() {
        if (this.field.disabled) {
        	return false;
        }
        if (! this.field.isDeepVisible()) {
        	return false;
        }
        if (this.field.hasClassName('skip-validation')) {
            return false;    
        }
        var t = $T(this.field);
        var value = $F(this.field);
        t.validationError = false;
            if (this.requiredCheck) {
                this.requiredCheck.call(this, value);
            }
            // Don't try to validate blank values; if the field is required, that error is already
            // noted and presented to the user. Don't skip dropdown fields. Skip fields with class 'skip-validation'
            if (!t.validationError && (!value.blank() || (this.field.type === 'select-one'))) {
                    var translated = this.translator(value);
                    // If Format went ok, perhaps do the other validations.
                    if (! t.validationError) {
                        this.field.fire(Tapestry.FIELD_VALIDATE_EVENT, {
                            value: value,
                            translated: translated
                        });
                    }
            }
            // Lastly, if no validation errors were found, show the okay icon
            if (! t.validationError) {
                this.field.showValidDecoration();
            }
        return t.validationError;
    }
});

Object.extend(Tapestry.FormEventManager.prototype, {
	handleSubmit : function(domevent) {
        var t = $T(this.form);
        t.validationError = false;
        // Locate elements that have an event manager (and therefore, validations)
        // and let those validations execute, which may result in calls to recordError().
        this.form.getElements().each(function(element) {
            var fem = $T(element).fieldEventManager;
            if (fem != undefined) {
                // Ask the FEM to validate input for the field, which fires
                // a number of events.
                fem.validateInput();
            }
        });
        // Allow observers to validate the form as a whole.  The FormEvent will be visible
        // as event.memo.  The Form will not be submitted if event.result is set to false (it defaults
        // to true).  Still trying to figure out what should get focus from this
        // kind of event.
        this.form.fire(Tapestry.FORM_VALIDATE_EVENT, this.form);
        if (t.validationError) {
            Event.stop(domevent); // Should be domevent.stop(), but that fails under IE
            // Because the submission failed, the last submit property is cleared,
            // since the form may be submitted for some other reason later.
            t.lastSubmit = null;
            
            //find field in error and scroll browser to that point in the page
            var elements = this.form.getElements();
            for ( var int = 0; int < elements.length; int++) {
				var element = elements[int];
				if ($T(element).validationError) {
					window.location.hash = $T(element).fieldEventManager.field.id;
					$($T(element).fieldEventManager.field.id).focus();
					$($T(element).fieldEventManager.field.id).select();
					break;
				}
				
			}
            
            return false;
            
        }
        this.form.fire(Tapestry.FORM_PREPARE_FOR_SUBMIT_EVENT, this.form);
        // This flag can be set to prevent the form from submitting normally.
        // This is used for some Ajax cases where the form submission must
        // run via Ajax.Request.
        if (this.preventSubmission) {
            // Prevent the normal submission.
            Event.stop(domevent);
            // Instead ...
            this.form.fire(Tapestry.FORM_PROCESS_SUBMIT_EVENT);
            return false;
        }  
        // Validation is OK, not doing Ajax, continue as planned.
        return true;
    }
});

Element.addMethods([
    'INPUT',
    'SELECT',
    'TEXTAREA'
    ], {
	showValidDecoration : function(element) {
	    element = $(element);
		
       	element.getFieldEventManager().showValidDecoration();
	    return element;
	}
});

var Validation = {
    initComponentFieldValidation : function() {
        $$(".componentField").each(function(element)
          {
              var fem = $(element).getFieldEventManager();
              element.observe("componentField:Change", function(event)
              {
                  fem.validateInput();
              }.bindAsEventListener(fem));
          });
    }
};

Tapestry.onDOMLoaded(function() {
    Validation.initComponentFieldValidation();
});
