
var jpForm = new Class({
	Implements: [Options, Events],
	form: null,
	fields: [],
	errorMessageContainer: null,
	submitInfoMessageContainer: null,
	options: {
		autoClear: false,
		valuesToConsiderNull: [],
		errorMessageContainerTag: 'p',
		errorMessageContainerId: 'jpFrmMessage',
		errorMessage: 'Please fill in all the required fields.',
		submitInfoMessageContainerId: 'jpFrmSendingMessage',
		submittingMessage: 'Submitting...',
		sentMessage: 'Thank you, your enquiry has been sent.',
		sendErrorMessage: 'Sorry, an error occured. Please try again later.',
		autoScrollOnError: false, //in case of error, scroll back to the top of the form (useful in case the error message displays out of the screen)
		inlineErrorMsg: true,
		ajax: false,
		loadingClass: 'loading',
		sentClass: 'sent',
		autoFocus: false, //focus the first field of the form
		resetToNull: false, //override default reset button behaviour
		/**
		 * Relations define a relation between two fields.
		 * - incremental-show:
		 * 		An input with numerical values determine whether other inputs
		 * 		(slaves) are displayed or not. A container can be defined
		 * 		(slavesContainer) and will be hidden when no slaves are to be
		 * 		displayed.
		 */
		relations: []
		/*
		relations: [
			{
				type: 'incremental-show',
				masterField: 'fld-master',
				slaveFieldsClass: 'slave',
				slavesContainer: 'slave-container'
			}
		]
		*/
		/*
		onSent: $empty
		*/
	},
	initialize: function(form, options) {
		this.form = $(form);
		if (!this.form) return;
		this.setOptions(options);
		
		if (this.form.hasClass('jpFrmNoInlineErrorMsg')) {
			this.options.inlineErrorMsg = false;
		}
		
		this.errorMessageContainer = $(this.options.errorMessageContainerId);
		
		//get all fields
		this.form.getElements('input, textarea, select').each(function(e) {
			this.fields.push(new jpField(e, this.options));
		}, this);
		
		//focus first field
		if (this.options.autoFocus && !this.options.autoClear && this.fields.length) {
			var i = 0;
			do {
				if (this.fields[i].type != 'hidden') {
					this.fields[i].element.focus();
					break;
				}
			}
			while (++i < this.fields.length);
		}
		
		if (this.options.ajax) {
			new Element('input', {type: 'hidden', name: 'ajax', value: '1'}).injectInside(this.form);
		}
		
		//submit event
		this.form.addEvent('submit', function(evt) {this.fireEvent('submit', evt);}.bindWithEvent(this));
		this.addEvent('submit', function(evt) {this.submit(evt);}.bindWithEvent(this));
		
		// reset button
		if (this.options.resetToNull) {
			this.form.getElements('input[type=reset]').each(function(e) {
				e.addEvent('click', function(evt) {
					evt.stop();
					this.reset()
				}.bindWithEvent(this));
			}, this);
		}
		
		//relations
		this.options.relations.each(function(e) {
			this.addRelation(e);
		}, this);
	},
	submit: function(evt) {
		//check fields
		var checked = true;
		for (var i = 0; i < this.fields.length; i++) {
			checked *= this.fields[i].check();
		}
		if (checked) {
			if (this.errorMessageContainer) {
				this.errorMessageContainer.dispose();
				this.errorMessageContainer = null;
			}
			for (var i = 0; i < this.fields.length; i++) {
				this.fields[i].clearNullFields();
			}
			if (this.options.ajax) {
				evt.stop();
				this.form.addClass(this.options.loadingClass);
				this.form.set('send', {
					url: this.form.get('action'),
					onComplete: function() {
						this.form.removeClass(this.options.loadingClass);
						this.form.addClass(this.options.sentClass);
					}.bind(this),
					onSuccess: function(responseText) {
						if (responseText == 'success') {
							this.submitInfoMessageContainer.set('html', this.options.sentMessage);
						}
						else {
							this.submitInfoMessageContainer.set('html', this.options.sendErrorMessage);
						}
					}.bind(this),
					onFailure: function() {
						this.submitInfoMessageContainer.set('html', this.options.sendErrorMessage);
					}.bind(this)
				});
				this.form.send();
				this.submitInfoMessageContainer = new Element(this.options.errorMessageContainerTag, {id: this.options.submitInfoMessageContainerId, html: this.options.submittingMessage});
				this.submitInfoMessageContainer.injectTop(this.form);
			}
			this.fireEvent('sent');
		}
		else {
			new Event(evt).stop();
			if (this.options.inlineErrorMsg) {
				if (!this.errorMessageContainer) {
					this.errorMessageContainer = new Element(this.options.errorMessageContainerTag, {id: this.options.errorMessageContainerId});
					this.errorMessageContainer.injectTop(this.form);
				}
				this.errorMessageContainer.set('html', this.options.errorMessage);
				if (this.options.autoScrollOnError) { //go back to the top of the form
					new Fx.Scroll(window, {wheelStops: false, duration: 100}).toElement(this.form);
				}
			}
			else {
				alert(this.options.errorMessage);
			}
			//focus first input containing errors
			for (var inputIndex = 0; inputIndex < this.fields.length; inputIndex++) {
				if (this.fields[inputIndex].hasError) {
					this.fields[inputIndex].element.focus();
					break;
				}
			}
		}
	},
	addRelation: function(relation) {
		var masterField = this.getField(relation.masterField);
		if (masterField) {
			switch(relation.type) {
				case 'incremental-show':
					var slaveElements = $$('.' + relation.slaveFieldsClass);
					var slaves = [];
					for (var i = 0; i < slaveElements.length; i++) {
						slaves.push(this.getField(slaveElements[i]));
					}
					var container = typeof relation.slavesContainer != 'undefined' ? relation.slavesContainer : null;
					masterField.addIncrementalShowRelation(slaves, container);
					break;
			}
		}
	},
	getField: function(field) {
		field = $(field);
		for (var i = 0; i < this.fields.length; i++) {
			if (this.fields[i].element == field) {
				return this.fields[i];
			}
		}
		return null;
	},
	/**
	 * Reset all fields value
	 */
	reset: function() {
		this.fields.each(function(field) {
			field.reset();
		}, this);
	}
});

var jpField = new Class({
	Implements: [Options, Events],
	element: null,
	label: null,
	errorMessageContainer: null,
	errorMessages: [],
	cleared: false,
	initialValue: '',
	type: null,
	hasError: false,
	hasFocus: false,
	labelTimer: null,
	slaves: [],
	slavesContainer: null,
	options: {
		autoClear: true,
		valuesToConsiderNull: [], //those values will be considered null
		requiredClass: 'jpFrmRequired',
		errorClass: 'jpFrmError',
		emailClass: 'jpFrmEmail',
		dateClass: 'jpFrmDate', //dd/mm/yyyy
		numericClass: 'jpFrmNumeric',
		labelDelay: 2000 //show label after this delay
		/*
		onError: $empty,
		onRemoveError: $empty,
		*/
	},
	initialize: function(element, options) {
		this.element = $(element);
		if (!this.element) return;
		this.setOptions(options);
		
		this.initialValue = this.element.value;
		
		var tag = this.element.get('tag');
		if (tag == 'input') {
			this.type = this.element.getAttribute('type') || 'text';
		}
		else {
			this.type = tag;
		}

		this.element.addEvent('blur', function() {
			this.hasFocus = false;
			if (this.hasError) {
				this.check();
			}
			this.hideLabel();
			if (this.options.autoClear) {
				this.setToDefault();
			}
		}.bind(this));
		
		this.label = new jpLabel(null, this);
		
		if (this.options.autoClear && (this.type == 'text' || this.type == 'password' || this.type == 'textarea')) {
			this.element.addEvent('focus', function() {
				this.hasFocus = true;
				this.showLabel();
				this.clearDefault();
			}.bind(this));
			
			this.element.addEvent('mouseenter', function() {
				this.showLabel();
			}.bindWithEvent(this));
			this.element.addEvent('mouseleave', function() {
				this.hideLabel();
			}.bindWithEvent(this));
		}
	},
	showLabel: function() {
		if (this.label) {
			$clear(this.labelTimer);
			this.label.show();
		}
	},
	hideLabel: function(force) {
		if (this.label) {
			if ((!this.hasFocus && ! this.hasError) || force) {
				$clear(this.labelTimer);
				this.label.hide();
			}
		}
	},
	getValue: function(getActualValue) {
		var value = this.element.value;
		if (!getActualValue) {
			var i = 0;
			while (i < this.options.valuesToConsiderNull.length) {
				if (value == this.options.valuesToConsiderNull[i]) {
					value = '';
					break;
				}
				i++;
			}
		}
		return value;
	},
	clearNullFields: function() {
		if (this.type != 'select' || !this.element.multiple) {
			this.element.value = this.getValue();
		}
	},
	isRequired: function() {
		return this.element.hasClass(this.options.requiredClass);
	},
	check: function() {
		var checked = true;
		this.errorMessages = [];
		if (this.isRequired() && !this.get('disabled')) {
			switch (this.type) {
				case 'text':
				case 'password':
				case 'textarea':
				case 'file':
				case 'select':
					var value = this.getValue();
					checked = value !== '' && value !== null;
					if (checked) {
						if (this.element.hasClass(this.options.emailClass)) {
							checked = value.match(/.+\@.+\..+/) ? true : false;
						}
						if (this.element.hasClass(this.options.numericClass)) {
							checked = value.match(/\d+/) ? true : false;
						}
						if (this.element.hasClass(this.options.dateClass)) {
							if (!value.match(/\d{1,2}\/\d{1,2}\/\d{4}/)) {
								checked = false;
								this.addErrorMessage('Date must be in the form dd/mm/yyyy');
							}
						}
					}
					break;
				case 'checkbox':
					checked = this.element.checked;
					break;
			}
		}
		
		if (!checked) {
			this.setError();
		}
		else {
			this.removeError();
		}
		return checked;
	},
	setError: function() {
		this.fireEvent('error');
		this.hasError = true;
		// highlight field
		this.element.addClass(this.options.errorClass);
		if (this.label) {
			this.label.setError();
			this.label.show();
		}
		if (this.errorMessages.length) {
			if (!this.errorMessageContainer) {
				this.errorMessageContainer = new Element('p', {'class': 'jpFrmFieldError'}).injectBefore(this.element.getParent());
			}
			this.errorMessageContainer.empty();
			this.errorMessageContainer.set('text', this.errorMessages.join("\n"));
		}
	},
	removeError: function() {
		this.fireEvent('removeError');
		this.hasError = false;
		this.element.removeClass(this.options.errorClass);
		if (this.label) {
			this.label.removeError();
			this.label.hide();
		}
	},
	addErrorMessage: function( message ) {
		this.errorMessages.push(message);
	},
	clearDefault: function() {
		if (this.element && !this.cleared) {
			this.element.value = '';
		}
		this.cleared = true;
	},
	setToDefault: function () {
		if (this.getValue(true) == '') {
			this.element.value = this.initialValue;
			this.cleared = false;
		}
	},
	get: function(attribute) {
		if (attribute == 'value') {
			return this.getValue();
		}
		return this.element.get(attribute);
	},
	hide: function() {
		this.element.getParent().addClass('hidden');
	},
	show: function() {
		this.element.getParent().removeClass('hidden');
	},
	addIncrementalShowRelation: function(slaves, slavesContainer) {
		this.slaves = slaves;
		this.slavesContainer = typeof slavesContainer != 'undefined' ? $(slavesContainer) : null;
		this.updateIncrementalShowRelation(true);
		this.element.addEvent('change', function() {
			this.updateIncrementalShowRelation();
		}.bindWithEvent(this));
	},
	updateIncrementalShowRelation: function(noFocusUpdate) {
		var numberOfItems = this.getValue();
		
		if (this.slavesContainer) {
			if (numberOfItems == 0) {
				this.slavesContainer.addClass('hidden');
			}
			else {
				this.slavesContainer.removeClass('hidden');
			}
		}
		
		for (var i = 0; i < this.slaves.length; i++) {
			if (i >= numberOfItems) {
				this.slaves[i].hide();
				this.slaves[i].element.set('disabled', true);
			}
			else {
				this.slaves[i].show();
				this.slaves[i].element.set('disabled', false);
				if (i == 0 && (typeof noFocusUpdate == 'undefined' || !noFocusUpdate)) {
					this.slaves[i].element.focus();
				}
			}
		}
	},
	reset: function() {
		switch (this.type) {
			case 'textbox':
			case 'textarea':
				this.element.set('value', '');
				 break;
			case 'checkbox':
			case 'radio':
				this.element.checked = false;
				break;
			case 'select':
				this.element.getElements('option').each(function(e) {
					e.selected = false;
				});
				break;
		}
	}
});

/**
 * Initialize with either the element or the related input
 */
var jpLabel = new Class({
	Implements: [Options, Events],
	element: null,
	hovering: false,
	options: {
		autohide: true,
		activeClass: 'jpFrmActive',
		errorClass: 'jpFrmError',
		hideOnHover: true
		/*
		onShow: $empty,
		onHide: $empty,
		onError: $empty,
		onRemoveError: $empty,
		*/
	},
	initialize: function (label, relatedInput) {
		if (label) {
			this.element = $(label);
		}
		if (!this.element && relatedInput) {
			relatedInput = typeof relatedInput == 'object' ? relatedInput : $(relatedInput);
			if (relatedInput) {
				//look for corresponding label
				this.element = $$('label[for=' + relatedInput.get('id') + ']');
			}
		}
		if (!this.element) return;
		
		this.element.addEvent('mouseenter', function() {
			this.onHover();
		}.bind(this));
		this.element.addEvent('mouseleave', function() {
			this.onHoverEnd();
		}.bind(this));
	},
	show: function() {
		if (this.options.autohide) {
			this.fireEvent('show');
			this.element.addClass(this.options.activeClass);
			this.timerId = null;
		}
	},
	hide: function() {
		if (this.options.autohide) {
			this.fireEvent('hide');
			this.element.removeClass(this.options.activeClass);
		}
	},
	onHover: function() {
		this.hovering = true;
	},
	onHoverEnd: function() {
		this.hovering = false;
	},
	setError: function() {
		this.fireEvent('error');
		this.element.addClass(this.options.errorClass);
	},
	removeError: function() {
		this.fireEvent('removeError');
		this.element.removeClass(this.options.errorClass);
	}
});
