var AjaxForm = Class.create({
	initialize: function(form) {
		
		// Get options
		this.options = Object.extend({
			styleInputs:		true,
			colorValid:			'#afda88',
			colorError:			'#f66e6e',
			colorValidText:		null,
			colorErrorText:		null,
			darkenAmount:		75,
			loadingDiv:			null,
			feedbackDiv:		null,
			feedbackError1:		'The submission page could not be found.',
			feedbackError2:		'There was a problem processing your submission.',
			feedbackSuccess:	'Thank you for your submission!',
			
			// Callback functions
			processError:		null,
			processSuccess:		null
		}, arguments[1] || {} );
		
		// Set defaults for text colors
		if(this.options.colorValidText == null)
		{
			var colorValid = new Color(this.options.colorValid);
			colorValid.darken(this.options.darkenAmount);
			this.options.colorValidText = colorValid.hex();
		}
		if(this.options.colorErrorText == null)
		{
			var colorError = new Color(this.options.colorError);
			colorError.darken(this.options.darkenAmount);
			this.options.colorErrorText = colorError.hex();
		}
		
		// Initialize members
		this.form = $(form);
		this.valid = false;
		this.iframe = null;
		this.submitting = false;
		this.formButtons  = new Hash();
		this.formElements = new Hash();
		this.formComplete = false;
		
		// Attach submit event
		this.bonSubmit = this.onSubmit.bindAsEventListener(this);
		this.form.observe('submit', this.bonSubmit);
		
		// Initialize all the elements
		this.form.getElements().each(function(element) {
			var name = element.readAttribute('name');
			var options = { colorValid: this.options.colorValid, 			colorError: this.options.colorError,
							colorValidText: this.options.colorValidText, 	colorErrorText: this.options.colorErrorText };
			
			// File uploads cannot be done via AJAX, we need an iframe and different submit handling
			if(element.nodeName.toLowerCase() == 'input' && element.readAttribute('type').toLowerCase() == 'file')
			{
				// We need to use an iframe in order to ajax with file uploads
				this.iframe = 'upload-iframe';
				if($(this.iframe) == null)
				{
					var iframe = Builder.node('iframe', { id: this.iframe, name: this.iframe, style: 'display:none' });
					$(document.body).insert(iframe);
				}
				
				// Set a callback for after the iframe loads
				this.bonIFrameSuccess = this.onIFrameSuccess.bind(this);
				$(this.iframe).observe('load', this.bonIFrameSuccess);
				
				// Now change the target of the form to load into the iframe
				this.form.writeAttribute({ target: this.iframe });
			}
			
			// Seperate out the submit buttons from the other elements
			if(element.nodeName.toLowerCase() == 'input' && element.readAttribute('type').toLowerCase() == 'submit') {
				this.formButtons.set(name, new FormElement(element, this.form, options)); }
			else {
				this.formElements.set(name, new FormElement(element, this.form, Object.extend(options, { blurCallback: this.checkValidation.bind(this) }))); }
			
		}, this);
		
		// If we are using iframes we need to track the name of the button pressed so we can insert it into the query
		if(this.iframe != null)
		{
			this.formButtons.each(function(pair) {
				pair.value.bonSubmitClick = this.onSubmitClick.bindAsEventListener(this);
				pair.value.element.observe('click', pair.value.bonSubmitClick);
			}, this);
		}
	},
	
	getValue: function(name) {
		var element = this.formElements.get(name);
		if(element) { element.getValue(); }
		return "";
	},
	
	setValue: function(name, value) {
		var element = this.formElements.get(name);
		if(element) { element.setValue(value); }
	},
	
	enableValidation: function(name, enable) {
		var element = this.formElements.get(name);
		if(element) { element.enableValidation(enable); }
	},
	
	setValidation: function(name, parameter) {
		var element = this.formElements.get(name);
		if(element) { element.setValidation(parameter); }
	},
	
	validate: function(name) {
		
		// If name was passed, validate one specific field
		if(name)
		{
			var element = this.formElements.get(name);
			if(element) { element.validate(); }
		}
		
		// Else, validate all the fields
		else
		{
			this.formElements.each(function(pair) {
				pair.value.validate();
			}, this);
		}
		
		// See if the whole form validates
		this.checkValidation();
	},
	
	checkValidation: function() {
		if(this.submitting == true) {
			return; }
		
		// Check each element (if enabled) to see if it is valid
		var newValid = true;
		this.formElements.each(function(pair) {
			newValid &= pair.value.isValid();
		}, this);
		
		// Now enable or disable all the buttons
		if(newValid != this.valid) {
			this.enableSubmit(newValid); }
		
		// Set the member var
		this.valid = newValid;
	},
	
	enableSubmit: function(enable) {
		this.formButtons.each(function(pair) {
			if(enable) {
				pair.value.enable(); }
			else {
				pair.value.disable(); }
		}, this);
	},
	
	reset: function() {
		this.form.reset();
		this.formElements.each(function(pair) {
			if(pair.value.type == 'textarea')
			{
				var value = pair.value.getValue();
				if(value.length == 1 && value.charCodeAt(0) == 160) {
					pair.value.setValue(''); }
			}
		}, this);
	},
	
	showFeedback: function(text, isError) {
		if(this.options.feedbackDiv != null)
		{
			var color = (isError == true) ? this.options.colorError : this.options.colorValid;
			var feedback = $(this.options.feedbackDiv);
			feedback.update(text);
			feedback.setStyle({backgroundColor: color});
			feedback.show();
		}
	},
	
	onSubmitClick: function(event) {
		this.submitName  = Event.element(event).readAttribute('name');
		this.submitValue = Event.element(event).readAttribute('value');
	},
	
	onIFrameSuccess: function() {
		if(this.submitting == false) {
			return; }
		
		var submitValue = $('__tempSubmitValue');
		if(submitValue != null) { submitValue.remove(); }
		
		this.onSuccess();
		this.onComplete();
	},
	
	onSubmit: function(event) {
		this.submitting = true;
		
		// We can't use the AJAX submission if we are using iframes for file uploads
		if(this.iframe == null)
		{
			// Stop the normal form handling
			event.stop();
			
			// Submit the request
			this.form.request({ onFailure: this.onFailure.bind(this), onSuccess: this.onSuccess.bind(this), onComplete: this.onComplete.bind(this) });
		}
		
		// The button can't be disabled when using iframes
		else
		{
			// So we have to insert a hidden filed to make sure its value gets passed
			if(this.submitName && this.submitValue)
			{
				var submitValue = Builder.node('input', { id: '__tempSubmitValue', type: 'hidden', name: this.submitName, value: this.submitValue });
				this.form.insert(submitValue);
			}
		}
		
		// This must be done after the request is submitted
		this.enableSubmit(false);
		
		// Show the loader
		if(this.options.loadingDiv != null) {
			$(this.options.loadingDiv).show(); }
	},
	onFailure: function(req) {
		// Show the error
		this.showFeedback(this.options.feedbackError1, true);
	},
	onSuccess: function(req) {
		var response = null;
		
		// If we are using an iframe we need to get the response from it
		if(this.iframe != null)
		{
			// First get the text
			var iframeText = null;
			var iframe = $(this.iframe);
			if(iframe.contentDocument) {
				iframeText = iframe.contentDocument.body.innerHTML; }
			else if (iframe.contentWindow) {
				iframeText = iframe.contentWindow.document.body.innerHTML; }
			else {
				iframeText = window.frames[id].document.body.innerHTML; }
			
			// Next parse the json
			if(typeof(iframeText) == 'string' && iframeText != '' && iframeText.isJSON()) {
				response = iframeText.evalJSON(); }
		}
		
		// We are expecting a JSON response for forms
		else if(req.responseJSON) {
			response = req.responseJSON; }
			
		// Just in case the proper headers weren't sent
		else if(req.responseText && req.responseText != '' && req.responseText.isJSON()) {
			response = req.responseText.evalJSON(); }
		
		// Parse the response
		if(response && response.error)
		{
			// Success
			if(response.error == 'false')
			{
				this.showFeedback(this.options.feedbackSuccess, false);
				this.reset();
			}
			
			// IE won't accept bools, it seems everything (json) must be a string
			else if(response.error == 'true' && response.errorText)
			{
				this.showFeedback(response.errorText, true);
			}
		}
		else
		{
			this.showFeedback(this.options.feedbackError2, true);
			//this.showFeedback(req.responseText, true);iframeText
			//this.showFeedback(iframe.contentDocument.body.innerHTML, true);
		}
	},
	onComplete: function() {
		// Re-enable the form and hide the loader
		if(this.options.loadingDiv != null) {
			$(this.options.loadingDiv).hide(); }
		this.submitting = false;
		this.validate();
	}
});

var FormElement = Class.create({
	initialize: function(element, form) {
		
		// Get options
		this.options = Object.extend({
			colorValid:			'#afda88',
			colorError:			'#f66e6e',
			colorValidText:		null,
			colorErrorText:		null,
			validate:			true,
			
			// Callback functions
			blurCallback:		null
		}, arguments[2] || {} );
		
		// Initialize members
		this.element = $(element);
		this.form  = $(form);
		this.valid = false;
		this.regex = null;
		this.range = null;
		this.type = null;
		this.name = element.readAttribute('name');
		this.label = $(this.name + "-label");
		
		// Store the original background and text colors
		this.originalBackgroundColor = (new Color(this.element.getStyle('backgroundColor'))).hex();
		this.originalTextColor = (new Color(this.element.getStyle('color'))).hex();
		
		// Get original label color
		if(this.label != null) {
			this.originalLabelColor = (new Color(this.label.getStyle('color'))).hex(); }
		
		// Figure out the type of the element
		if(element.nodeName.toLowerCase() == 'input') {
			this.type = element.readAttribute('type').toLowerCase(); }
		else {
			this.type = element.nodeName.toLowerCase(); }
		
		var setBorder = false;
		
		// For submit button, deactive by default
		if(this.type == 'submit')
		{
			this.valid = true;
			this.element.disable();
		}
		
		// For a textarea, clear any whitespace
		else if(this.type == 'textarea')
		{
			setBorder = true;
			var value = this.getValue();
			if(value.length == 1 && value.charCodeAt(0) == 160) {
				this.setValue(''); }
			
			this.bonBlur = this.onBlur.bindAsEventListener(this);
			this.element.observe('blur', this.bonBlur);
		}
		
		// Don't want to validate checkboxes or hidden fields by default
		else if(this.type == 'checkbox' || this.type == 'hidden')
		{
			this.options.validate = false;
			this.valid = true;
		}
		
		// For a file input we want to validate onChange
		else if(this.type == 'file')
		{
			this.bonBlur = this.onBlur.bindAsEventListener(this);
			this.element.observe('change', this.bonBlur);
		}
		
		// For a select we want to validate onChange
		else if(this.type == 'select')
		{
			setBorder = true;
			this.bonBlur = this.onBlur.bindAsEventListener(this);
			this.element.observe('change', this.bonBlur);
		}
		
		// For everything else, set onBlur for validation
		else
		{
			setBorder = true;
			this.bonBlur = this.onBlur.bindAsEventListener(this);
			this.element.observe('blur', this.bonBlur);
		}
		
		var border = this.element.getStyle('border');
		if(this.options.styleInputs == true && setBorder == true && (border == '' || border == undefined)) {
			this.element.setStyle({border: 'solid 1px #a5acb2'}); }
			
		this.validate();
	},
	
	getValue: function() {
		return $F(this.element);
	},
	
	setValue: function(value) {
		this.element.setValue(value);
	},
	
	enabled: function() {
		return !(this.element.disabled == true || this.element.readAttribute('readonly') != null);
	},
	
	enable: function() {
		this.element.enable();
	},
	
	disable: function() {
		this.element.disable();
	},
	
	isValid: function() {
		if(this.element.disabled == true || this.element.readAttribute('readonly') != null) {
			return true; }
		else {
			return this.valid; }
	},
	
	enableValidation: function(enable) {
		this.options.validate = enable;
		
		// If we are enabling validation, set the current state
		if(enable == true) {
			this.validate(); }
			
		// If we are disabling validation, mark valid and update display
		else
		{
			this.valid = true;
			this.updateColor();
		}
	},
	
	setValidation: function(parameter) {
		if(parameter instanceof RegExp) {
			this.regex = parameter; }
		else if(parameter instanceof Array && parameter.length == 2) {
			this.range = parameter; }
	},
	
	validate: function() {
		if(this.options.validate == false || this.element.disabled == true || this.element.readAttribute('readonly') != null) {
			return; }
		
		// See if data has been entered
		this.valid = this.element.present();
		
		// Check any custom validation
		if(this.regex != null) {
			this.valid = this.regex.test(this.getValue()); }
		else if(this.range != null)
		{
			var valueInt   = parseInt(this.getValue());
			var valueFloat = parseFloat(this.getValue());
			if(valueInt/valueFloat == 1 && valueInt >= this.range[0] && valueInt <= this.range[1]) {
				this.valid = true; }
			else {
				this.valid = false; }
		}
		
		// Update the display
		this.updateColor();
	},
	
	updateColor: function() {
		
		if(this.options.styleInputs == true)
		{
			// File inputs don't like their background being set
			if(this.type == 'file')
			{
				if(this.options.validate == false || this.getValue() == "") {
					color = this.originalTextColor; }
				else if(this.valid == true) {
					color = this.options.colorValidText; }
				else {
					color = this.options.colorErrorText; }
				
				this.element.setStyle({ color: color });
			}
			else
			{
				if(this.options.validate == false || this.getValue() == "") {
					color = this.originalBackgroundColor; }
				else if(this.valid == true) {
					color = this.options.colorValid; }
				else {
					color = this.options.colorError; }
				
				this.element.setStyle({ backgroundColor: color });
			}
		}
		
		if(this.label != null && this.options.validate == true)
		{
			if(this.valid == true) {
				this.label.setStyle({ color: this.options.colorValidText }); }
			else {
				this.label.setStyle({ color: this.options.colorErrorText }); }
		}
		else if(this.label != null && this.options.validate == false) {
			this.label.setStyle({ color: this.originalLabelColor }); }
	},
	
	onBlur: function(event) {
		
		// First validate
		this.validate();
		
		// Then call the blur callback function
		if(this.options.blurCallback != null) {
			this.options.blurCallback(); }
	}
});