/*!
 * plum.Form v1.3: Styling web forms
 *
 * Copyright 2011 RoboCréatif, LLC
 * <http://robocreatif.com>
 *
 * Date: August 6, 2011
 */

var plum = plum || {};

String.prototype.plum = Number.prototype.plum = jQuery.fn.plum = function(callback, options)
{
	var action = callback.split('.'), secondary;
	callback = action[0];
	if (action.length > 1) {
		secondary = options;
		options = action[1];
	}
	return typeof plum[callback] === 'function' ? plum[callback].call(this, options, secondary) : this;
};

(function ($, plum) {

	// Checks for browser support for uploading files, plum containers
	// and invalid fields
	$.support.file = window.File && window.FileList;
	$.support.filexhr = window.XMLHttpRequestUpload;
	$.expr[':'].plum = function (a) { return !!$(a).data('plum'); };
	$.expr[':'].invalid = function (a) { return !!$(a).data('invalid'); };

	// Older browsers need to submit forms to a hidden iframe to support
	// file uploads via ajax. What a shame
	if ((!$.support.file || !$.support.filexhr) && !$('iframe[name="plum-form"]').length) {
		var iframe;
		$(function () {
			iframe = $('<iframe name="plum-form">').attr('src', 'about:blank').css({
				border: 0,
				left: '-9999em',
				height: 0,
				position: 'absolute',
				top: '-9999em',
				width: 0
			}).appendTo('body');
		});
	}

	// Applies plum.Form to each form that contains selected elements
	function Form (form, options) {

		// Configure the form's options
		$.extend(true, this.options, options);
		var plum = this, c = this.options.classes;

		this.form = form;
		form.action = this.options.action || form.action || window.location.href;
		form.plum.each(function() {

			if ($(this).data('plum')) {
				return true;
			}

			// Mmm, cached variables
			var elem = $(this),
			type = elem.attr('type') || this.type,
			node = this.nodeName.toLowerCase(),
			pos = elem.css('position'),
			label = elem.parent(),
			wrapper,
			mouse;

			if (!label.is('label')) {
				label = $('label[for="' + this.id + '"]');
			}

			// Create a plum for each element based on its properties and style
			this.wrapper = elem
				.css({ overflow: 'visible' })
				.wrap($('<div>', {
				'class': 'plum-form'
					+ ' ' + (c[type] || '')
					+ ' ' + (c[node] || '')
					+ ' ' + (this.disabled && c.disabled || '')
					+ ' ' + (this.multiple && c.multiple || '')
					+ ' ' + (this.selected && c.selected || '')
					+ ' ' + (this.checked && c.checked || ''),
				title: this.title,
				dir: this.dir,
				css: {
					cssFloat: elem.css('float'),
					position: pos === 'static' ? 'relative' : pos,
					width: /^(?:button|checkbox|file|submit|reset|radio)$/.test(type)
						|| ($.browser.msie && parseInt($.browser.version, 10) === 7) ? ''
						: elem.css('width')
				}
			}))
				.data('classes', c)
				.data('plum', true)
				.bind({
					focus: function () { this.wrapper.addClass(c.focus); },
					blur: function () { this.wrapper.removeClass(c.focus); }
				})
				.parent()
				.bind('mousedown mouseup', function (e) {
					mouse = e.type === 'mousedown';
					$(this).toggleClass(c.active);
				})
				.bind({
					mouseleave: function () {
						$(this).removeClass(c.active);
					},
					mouseenter: function (e) {
						if (mouse) {
							$(this).addClass(c.active);
						}
					}
				})
				.css({ verticalAlign: 'top' });

			elem.css({ width: this.wrapper.css('width') });

			// Run type or node methods on each plum
			plum[(typeof plum[type] === 'function' ? type
				: typeof plum[node] === 'function' ? node
				: 'input')](elem);

			// Apply the label effects
			if (label.length) {
				plum.inFieldLabels(elem, label);
			}

		});

		// Initial state of checkbox group handlers
		this.checkAllBoxes();

		return $(form)
			.unbind('submit', this.submitForm)
			.bind('submit', { plum: this }, this.submitForm);

	};
	Form.prototype = {

		form: null,
		queue: [],
		options: {

			action: null,
			ajax: false,
			complete: function () { },
			classes: {
				active: 'active',
				arrow: 'select-arrow',
				button: 'button',
				checkbox: 'checkbox',
				checked: 'checked',
				closed: 'closed',
				color: 'color',
				container: 'select-container',
				date: 'date',
				datetime: 'datetime',
				disabled: 'disabled',
				file: 'file',
				filelist: 'filelist',
				focus: 'focus',
				email: 'email',
				error: 'error',
				hover: 'hover',
				info: 'info',
				input: 'input',
				label: 'label',
				loading: 'loading',
				mixed: 'mixed',
				month: 'month',
				multiple: 'multiple',
				number: 'number',
				open: 'open',
				optgroup: 'optgroup',
				option: 'option',
				password: 'password',
				progress: 'progress',
				radio: 'radio',
				range: 'range',
				remove: 'remove',
				reset: 'reset',
				submit: 'submit',
				text: 'text',
				textarea: 'textarea',
				select: 'select',
				search: 'search',
				selected: 'selected',
				single: 'single',
				success: 'success',
				tel: 'tel',
				url: 'url',
				value: 'select-value',
				waiting: 'waiting',
				week: 'week',
				wrapper: 'select-wrapper'
			},
			file: {
				button: 'Choose a file...',
				complete: function () { },
				errorsize: 'Please choose a file smaller than {filesize}.',
				errortype: 'This file type is not allowed.',
				files: 0,
				html: '<span class="filename">{filename}</span>'
					+ '<span class="remove">&times;</span>'
					+ '<span class="filesize">{filesize}</span>'
					+ '<div class="progress"><div></div></div>',
				progress: function (e) {
					e.progressbar.children().stop(true, true)
						.animate({ width: e.percent + '%' }, 150);
				},
				size: 0,
				start: function () { },
				types: []
			},
			json: false,
			labels: false,
			reset: false,
			submit: function () { }

		},

		checkbox: function (elem) {

			var plum = this,
			c = this.options.classes,
			form = this.form;
			elem
				.bind('click', function () {

					var boxes, group = elem.hasClass('check-all')
						&& elem.attr('class').match(/group-([^\s]+)/)[1];

					if (!group) {
						this.wrapper.toggleClass(c.checked);
						plum.checkAllBoxes(this.name);
					} else {
						form.plum.filter(function () {
							return this.type === 'checkbox'
								&& !this.disabled
								&& (this.name === group || $(this).hasClass('group-' + group));
						}).each(function () {
							if (elem[0].checked || this.wrapper.hasClass(c.mixed)) {
								this.checked = true;
								this.wrapper.removeClass(c.mixed).addClass(c.checked);
							} else {
								this.checked = false;
								this.wrapper.removeClass(c.checked + ' ' + c.mixed);
							}
						});
					}

				})
				.css({
					left: '50%',
					marginLeft: -elem[0].offsetWidth / 2,
					marginTop: -elem[0].offsetHeight / 2,
					opacity: 0,
					position: 'absolute',
					top: '50%'
				})
				.parent()
				.css({ verticalAlign: '' });

		},
		file: function (elem) {

			var plum = this,
			a = this.options.ajax,
			c = this.options.classes,
			f = this.options.file,

			file = {

				size: function (size) {

					size   = { B: size };
					size.K = size.B / 1024;
					size.M = size.K / 1024;
					size.G = size.M / 1024;
					return size.G > 1 ? Math.round(size.G) + ' GB'
						: size.M > 1 ? Math.round(size.M) + ' MB'
						: size.K > 1 ? Math.round(size.K) + ' KB'
						: size.B > 0 ? size.B + ' bytes'
						: '';

				},
				add: function () {

					// Create a new item in the file list
					var li = $('<li>', {
						'class': (this.error ? c.error : c.waiting)
							+ ' plum-upload-' + (index - 1),
						css: { display: 'none' },
						html: f.html
							.replace(/\{filename\}/g, this.name)
							.replace(/\{filesize\}/g, file.size(this.size))
							.replace(/\{filetype\}/g, this.type)
							+ this.error
							+ '<div style="clear:both"></div>'
					}).appendTo(filelist).fadeIn(300);

					// When clicking the "remove" button in each file, its
					// relevant file list and queue position are removed
					$('.' + c.remove, li).bind('click', function () {

						var i = li.attr('class').match(/(?:(?:.+\s+)?)?plum-upload-([\d]+)/)[1],
						file = $(':file.plum-upload-' + i);

						li.fadeOut(300, function () { li.remove(); });
						if (typeof plum.queue[i] !== 'undefined') {
							plum.queue.splice(i, 1);
						}
						if (!$.support.file) {
							file.remove();
						}

					});

					// If an error occurs, remove the progress bar. Otherwise,
					// make sure it's empty
					if (this.error || !$.support.filexhr) {
						$('.' + c.progress, li).remove();
					} else {
						$('.' + c.progress, li).children().css({ width: 0 });
					}

				},
				change: function () {

					var i = 0, l,
					elem = $(this),
					files,
					props = { name: this.value, size: '', type: '', error: '' };

					// Hooray, awesome browser
					if ($.support.file) {
						if (!a) {
							plum.queue = [];
							filelist.children().remove();
						}
						for (l = this.files.length; i < l; i++) {
							// Too many files, stop adding
							if (f.files && plum.queue.length === f.files) {
								break;
							}

							// Set the file properties
							files = this.files[i];
							props.name = files.name || files.fileName;
							props.size = files.size || files.fileSize;
							props.type = files.type || files.fileType;
							if (f.types.length && $.inArray(props.type, f.types) < 0) {
								props.error = '<div>' + f.errortype + '</div>';
							} else if (f.size && props.size > f.size) {
								props.error = '<div>' + f.errorsize + '</div>';
							}

							// No errors occured, so the file can be queued
							if (!props.error) {
								index++;
								plum.queue.push(files);
							}

							// Add the file to the file list
							file.add.call(props);
						}

					// Stupid browser and too many files
					} else if (f.files && plum.queue.length === f.files) {
						return false;

					// Stupid browser. Everything is assumed to be OK
					} else {
						index++;
						plum.queue.push(props.name);
						file.add.call(props);

						// Hide the previous file field and create a new one
						elem = elem.css({ zIndex: -998 })
							.unbind('change', file.change)
							.after(elem.clone().val(''))
							.next()
							.data('plum', true)
							.removeClass('plum-upload-' + (index - 1))
							.addClass('plum-upload-' + index)
							.css({ zIndex: 998 })
							.bind('mousedown mouseup', function () {
								$(this).parent().toggleClass(c.active);
							})
							.bind('change', file.change);

					}

				}

			},

			index = 0,
			maxSize = file.size(f.size),
			filelist = $('<ul>', { 'class': c.filelist }).insertAfter(elem),

			// The original file field is wrapped with a new plum and given
			// a pseudo-button
			mouse,
			original = elem
				.attr('multiple', true)
				.addClass('plum-upload-' + index)
				.css({ opacity: 0, position: 'absolute', width: 50 })
				.wrap($('<div>', {
					'class': 'plum-form ' + c.input + ' ' + c.button,
					css: {
						overflow: 'hidden',
						position: 'relative'
					}
				}).bind({
					mouseover: function () {
						$(this).toggleClass(c.hover);
						if (mouse) {
							$(this).addClass(c.active);
						}
					},
					mouseout: function () {
						$(this).toggleClass(c.hover).removeClass(c.active);
					},
					mousemove: function (e) {
						var elem = $(this), file = $(':file', this).eq(-1);
						file.css({
							left: -file.outerWidth(),
							marginLeft: e.pageX - elem.offset().left + 25,
							top: e.pageY - elem.offset().top - 10
						});
					}
				}))
				.before('<button style="overflow:visible" type="button" tabindex="-1">'
					+ f.button
					+ '</button>'
				)
				.unbind('mousedown mouseup')
				.bind({
					focus: function () {
						this.wrapper.removeClass(c.focus);
						$(this).parent().addClass(c.focus);
					},
					blur: function () {
						$(this).parent().removeClass(c.focus);
					}
				})
				.bind('mousedown mouseup', function (e) {
					mouse = e.type === 'mousedown';
					this.wrapper.toggleClass(c.active);
				})
				.bind('change', file.change);

		},
		input: function (elem) {

			var c = this.options.classes;
			switch (elem[0].type) {
				case 'textarea':
					return elem.css({ resize: 'none', verticalAlign: 'bottom' });
				case 'button':
				case 'submit':
					return elem.attr('formnovalidate', true);
				default:
					return elem.css('verticalAlign', 'bottom').each(function () {
						this.wrapper.addClass(c.text);
					});
			}

		},
		radio: function (elem) {

			var c = this.options.classes,
			form = this.form,
			name = elem
				.bind('click', function () {
					form.plum.filter(function () {
						return this.type === 'radio' && this.name === name;
					}).each(function () {
						this.wrapper.removeClass(c.checked);
					});
					this.wrapper.addClass(c.checked);
				})
				.css({
					left: '50%',
					marginLeft: -elem[0].offsetWidth / 2,
					marginTop: -elem[0].offsetHeight / 2,
					opacity: 0,
					position: 'absolute',
					top: '50%'
				})[0].name;
			elem[0].wrapper.css({ verticalAlign: '' });

		},
		reset: function (elem) {

			var plum = this;
			elem.bind('click', function (e) {
				e.preventDefault();
				plum.resetForm();
			});

		},
		select: function (elem) {

			var c = this.options.classes,
			index = 0,
			i = 0,
			wrapper = elem[0].wrapper,
			disabled = false,
			multiple = elem[0].multiple,
			size = elem[0].size || (multiple ? 5 : 10),
			open = !!multiple,
			closed = !multiple,
			optionslist,
			search = '',
			escKey = false,

			// Event methods
			select = {

				click: function (e) {

					i = menuoptions.index(this);
					if (e) {
						e.preventDefault();
					}

					if (multiple) {
						elem.trigger('focus');
						select[e.shiftKey ? 'shift' : e.ctrlKey ? 'ctrl' : 'one'](this);
					} else {
						select.one(this);
					}
					if ((closed || (multiple && !e.shiftKey)) && index !== i) {
						index = i;
						elem.trigger('change');
					}

				},
				close: function () {

					if (!escKey && index !== i) {
						index = i;
						elem.trigger('change');
					} else {
						select.click.call(menuoptions.eq(index));
					}
					closed = true;
					open = false;
					escKey = false;
					elem.trigger('close');
					return menu.stop(true, true).slideUp(150, function () {
						wrapper.css('zIndex', '').addClass(c.closed).removeClass(c.open);
						menu.css('marginTop', 0);
					});

				},
				open: function () {

					open = true;
					closed = false;
					elem.trigger('focus').trigger('open');

					// To prevent the menu from doing stupid things to the
					// document.height, the top margin should be calculated
					// to keep the top and bottom of the menu at least 25
					// pixels from the top and bottom of the page
					var docHeight = $(document).height(),
					marginTop = -value.outerHeight(true)
						- parseInt(elem[0].wrapper.css('borderTopWidth'), 10);
					if (wrapper[0].offsetTop + height + 50 > docHeight) {
						marginTop = docHeight - (wrapper[0].offsetTop + height) - 50;
					}
					if (marginTop * -1 > wrapper[0].offsetTop + 25) {
						marginTop = -wrapper[0].offsetTop + 25;
					}

					// Animate the menu and put the wrapper above everything
					wrapper.css({ zIndex: 999 }).addClass(c.open).removeClass(c.closed);
					menu.stop(true, true)
						.animate({ marginTop: marginTop }, 150)
						.slideDown(150, function () {
							menu.scrollTop(index * maxHeight / size);
						});

				},
				keydown: function (e) {

					switch (e.which) {
						case 8:
							e.preventDefault();
							search = !search ? '' : search.substring(0, search.length - 1);
							select.search(e);
							break;
						case 9:
							search = '';
							if (!multiple) {
								select.close();
							}
							break;
						case 27:
							search = '';
							if (!multiple) {
								escKey = true;
								select.close();
							}
							break;
						case 38:
						case 40:
							e.ctrlKey = false;
							i = e.which === 38
								? (i - 1 < 0 ? 0 : i - 1)
								: (i + 1 >= menuoptions.length ? menuoptions.length - 1 : i + 1);
							select.click.call(menuoptions.eq(i)[0], e);
							break;
						default:
							break;
					}

				},
				keypress: function (e) {

					var i = 0, l;
					if (!e.which) {
						search = '';
						return this;
					}
					if (e.which === 13) {
						search = '';
						if (!multiple) {
							select[wrapper.hasClass(c.open) ? 'close' : 'open']();
						}
						return this;
					}
					e.preventDefault();
					search += String.fromCharCode(e.which);
					select.search(e);

				},
				ctrl: function () {

					original[i].selected = !original[i].selected;
					menuoptions.eq(i).toggleClass(c.selected);

				},
				search: function (e) {

					var j = 0, l = optionvalues.length;
					for (; j < l; j++) {
						if (optionvalues[j].substring(0, search.length) === search) {
							return select.click.call(menuoptions.eq(j)[0], e);
						}
					}

				},
				shift: function () {

					var s = index > i ? i : index, e = index > i ? index : i;
					original.each(function () { this.selected = false; });
					menuoptions.removeClass(c.selected);
					for (; s <= e;) {
						original[s].selected = true;
						menuoptions.eq(s++).addClass(c.selected);
					}

				},
				one: function (elem) {

					original.each(function () { this.selected = false; })[i].selected = true;
					menuoptions.removeClass(c.selected).eq(i).addClass(c.selected);
					selected.text($(elem).text());
					menu.scrollTop(i * maxHeight / size);

				}

			},

			// The menu's internal wrapper
			innerwrapper = $('<div>', {
				'class': c.wrapper
			})
				.prependTo(wrapper),

			// The selected value of select-one menus
			value = multiple ? $() : $('<div>', {
					'class': c.value,
					css: { position: 'relative' },
					html: '<div></div><div class="' + c.arrow + '"></div>'
				})
				.appendTo(innerwrapper)
				.css({ verticalAlign: 'bottom' }),
			selected = value.children('div:first-child'),

			// Menu dimensions
			height = 0,
			width = elem[0].offsetWidth,
			maxHeight = 0,

			// The list of menu options and groups
			menu = $('<ul>', {
				'class': c.container,
				css: {
					overflowX: 'hidden',
					overflowY: 'scroll',
					position: 'relative',
					whiteSpace: 'nowrap',
					width: width
				}
			}).appendTo(innerwrapper),

			// Builds the menu
			original = null,
			menuoptions = null,
			optionvalues = [],
			compile = function () {
				var node = this.nodeName.toLowerCase(),
				text = this.label || this.textContent || this.innerText;
				optionslist += '<li class="' + c[node]
					+ ' ' + (this.disabled && c.disabled || '')
					+ ' ' + (this.selected && c.selected || '')
					+ '">';
				if (node === 'option') {
					optionslist += text;
					if (this.selected) {
						selected.text(text);
					}
				} else {
					optionslist += '<label>' + text + '</label><ul>';
					$(this).children().each(compile);
					optionslist += '</ul>';
				}
				optionslist += '</li>';
			},
			rebuild = function () {
				optionslist = '';
				search = '';
				optionvalues = [];
				disabled = elem[0].disabled;
				elem.children().each(compile);
				menu.show().html(optionslist);
				maxHeight = $('li.' + c.option + ':eq(0)', menu).outerHeight(true) * size;
				menu.css({
					display: multiple ? 'block' : 'none',
					maxHeight: maxHeight
				});
				height = menu.outerHeight(true);
				original = $('option:not(:disabled)', elem);
				menuoptions = menu.find('li.' + c.option + ':not(.' + c.disabled + ')')
					.bind('click', select.click)
					.each(function () {
						optionvalues.push($(this).text().toLowerCase())
					});
				if (!multiple && !selected.text()) {
					selected.text(
						elem.find('option[selected]').text() ||
						elem.find('option:eq(0)').text()
					);
				}
				if (disabled) {
					wrapper.addClass(c.disabled);
				} else {
					wrapper.removeClass(c.disabled);
				}
				wrapper.css({ width: menu.outerWidth(true) });
				return true;
			};


			// Hide that pesky old menu, listen for keyboard activity, and adjust
			// the wrapper to its proper state
			elem
				.css({
					opacity: 0,
					position: 'absolute',
					top: 0,
					zIndex: -999
				})
				.bind({
					keydown: select.keydown,
					keypress: select.keypress,
					rebuild: rebuild
				});
			wrapper
				.addClass(multiple ? c.multiple + ' ' + c.open : c.single + ' ' + c.closed)
				.bind('mousedown', false);

			// Build the menu
			rebuild();

			if (!disabled) {
				menuoptions.each(function (k) {
					if ($(this).hasClass(c.selected)) {
						index = i;
						i = k;
						return false;
					}
				});
			}
			if (!multiple) {

				// The menu needs to be positioned absolutely for a nice overflow
				menu.css({ position: 'absolute' });

				// Document clicking determines if a menu should be opened or
				// closed, so it only applies to select-one menus
				$(document).bind('click', function (e) {

					e = $(e.target);

					// Not clicking on this menu, go away
					if (e.closest('div.plum-form.' + c.select)[0] !== wrapper[0]) {
						if (!multiple && open) {
							select.close();
						}
						return this;
					}

					// Disabled menus, disabled options and groups don't need to
					// be processed
					if (disabled || e.hasClass(c.disabled) || e.is('label')) {
						return this;
					}

					select[open ? 'close' : 'open']();

				});

			}

		},

		checkAllBoxes: function (group) {

			var c = this.options.classes, form = this.form;
			form.plum.filter(function () {
				return this.type === 'checkbox'
					&& !this.disabled
					&& $(this).hasClass('check-all')
					&& (!group || $(this).hasClass('group-' + group));
			}).each(function () {

				var elem = $(this),
				wrapper = elem.parent(),
				group = elem.attr('class').match(/(?:(?:.+\s+)+)?group-([^\s]+)/)[1],
				boxes = form.plum.filter(function () {
					return this.type === 'checkbox'
						&& !this.disabled
						&& this.name === group
						&& !$(this).hasClass('check-all');
				}),
				checked = boxes.filter(function () { return this.checked; });

				if (checked.length === 0) {
					this.checked = false;
					wrapper.removeClass(c.checked + ' ' + c.mixed);
				} else {
					this.checked = true;
					if (checked.length === boxes.length) {
						wrapper.removeClass(c.mixed).addClass(c.checked);
					} else {
						wrapper.addClass(c.checked + ' ' + c.mixed);
					}
				}

			});

		},
		inFieldLabels: function (elem, label) {

			var options = this.options, inner;

			// Bind hover events to the label
			label.bind('mouseover mouseout', function () {
				elem.parent().toggleClass(options.classes.hover);
			});

			// These types don't support in-field labels
			if (
				!options.labels ||
				/^(?:button|checkbox|file|select-one|select-multiple|submit|radio|reset)$/
					.test(elem[0].type.toLowerCase())
			) {
				return;
			}

			// Create the new label
			inner = $('<label>', {
				'class': options.classes.label,
				css: {
					display: 'block',
					height: elem[0].clientHeight,
					left: -parseInt(elem.css('borderLeftWidth'), 10)
						+ parseInt(elem.css('paddingLeft'), 10),
					position: 'absolute',
					top: -parseInt(elem.css('borderTopWidth'), 10),
					whiteSpace: 'nowrap'
				},
				text: label.text()
			})
			.appendTo(elem.parent())
			.bind('mousedown', function () {
				elem.trigger('focus');
				return false;
			});

			// Get rid of the original label
			if (elem.parent().parent().is('label')) {
				elem.parent().insertAfter(label);
			}
			label.remove();

			// Check for an initial value
			if (elem[0].value) {
				inner.hide().css({ opacity: 0 });
			}

			// Listen for activity on the field, and adjust the visibility
			// of the label accordingly
			elem.bind('focus blur', function (e) {
				if (!this.value) {
					inner.show().stop().animate({
						opacity: e.type === 'focus' ? 0.3 : 1
					}, 250);
				} else {
					inner.hide().css({ opacity: 0 });
				}
			}).bind('keypress', function (e) {
				if (e.which) {
					inner.hide().css({ opacity: 0 });
				}
			});

		},
		submitForm: function (e) {

			var plum = e.data.plum,
			options = plum.options,
			form = $(this),
			files = $(':file:plum', this).parent().parent(),
			filelist = files.find('li'),
			c = options.classes;

			// Validate the form and run the submit callback
			this.plum.trigger('blur');
			if (
				$(':input:plum:invalid', this).plum('form.shake').length ||
				options.submit.call(this) === false
			) {
				return false;
			}

			// No AJAX, submit the form normally
			if (!options.ajax) {
				return this;
			}
			$(':submit', this).attr('disabled', true);

			// Modern browsers rock
			if ($.support.filexhr) {
				e.preventDefault();
				plum.uploadFile(filelist, function () {
					$.ajax(form[0].action, {
						type: form[0].method || 'GET',
						data: form.serialize(),
						dataType: options.json ? 'json' : 'html',
						success: function (e) {
							if (options.reset) {
								plum.resetForm();
							}
							$(':submit', form).attr('disabled', false);
							options.complete.call(form[0], e);
						}
					});
				});
				return this;
			}

			// Old browsers do not rock
			this.target = 'plum-form';
			filelist.filter(function () {
				return !$(this).hasClass(c.error);
			}).toggleClass(c.waiting + ' ' + c.loading);
			iframe.unbind('load').bind('load', function () {
				if (options.reset) {
					plum.resetForm();
				}
				$(':submit', form).attr('disabled', false);
				options.complete.call(form[0], $(this).contents().find('body').html());
				files.each(function () {
					$(':file', this).slice(0, -1).remove();
					$('ul.' + c.filelist + ' li', this).fadeOut(300, function () {
						$(this).remove();
					});
				});
			});
			return this;

		},
		resetForm: function () {

			var plum = this, c = this.options.classes;
			this.form.reset();
			this.form.plum.each(function () {

				var e = $(this), w = e.parent(), files, menu;
				switch (this.type) {
					case 'checkbox':
					case 'radio':
						if (this.checked) {
							w.addClass(c.checked);
						} else {
							w.removeClass(c.checked + ' ' + c.mixed);
						}
						break;
					case 'file':
						plum.queue = [];
						files = w.find(':file');
						files.each(function (k) {
							if (k < files.length - 1) {
								$(this).remove();
							}
						});
						$('li', w.next()).each(function () {
							var li = $(this).fadeOut(300, function () { li.remove(); });
						});
						break;
					case 'reset':
						break;
					case 'select-multiple':
					case 'select-one':
						menu = w.find('li.' + c.option).removeClass(c.selected);
						$('option', this).each(function (i) {
							var s = $(this), m = menu.eq(i);
							if (this.selected) {
								m.addClass(c.selected);
								if (e[0].type === 'select-one') {
									s.closest('div.plum-form')
										.find('div.' + c.value + ' div:first-child')
										.text(s.text());
								}
							}
						});
						break;
					default:
						e.trigger('blur');
						break;
				}
				w.children('div.' + c.info).removeClass(c.error + ' ' + c.success);

			});
			this.checkAllBoxes();

		},
		uploadFile: function (filelist, callback) {

			if (!filelist.length) {
				return callback();
			}

			var plum = this,
			options = this.options,
			c = options.classes,
			li = filelist.eq(0),
			file = this.queue.shift(),
			progressbar = $('.' + c.progress, li).slideDown(300),
			xhr = new XMLHttpRequest();

			// Upload the file
			xhr.upload.addEventListener('loadstart', function (e) {

				li.toggleClass(c.waiting + ' ' + c.loading);
				options.file.start.call(li, $.extend(e, {
					progressbar: progressbar,
					percent: e.loaded / e.total * 100
				}));

			}, false);
			xhr.upload.addEventListener('progress', function (e) {

				options.file.progress.call(li, $.extend(e, {
					progressbar: progressbar,
					percent: e.loaded / e.total * 100
				}));

			}, false);
			xhr.upload.addEventListener('load', function (e) {

				options.file.complete.call(li, $.extend(e, {
					progressbar: progressbar,
					percent: 100
				}));
				li.fadeOut(300, function () {
					if (plum.queue.length) {
						plum.uploadFile(filelist.slice(1), callback);
					} else {
						callback();
					}
				});

			}, false);
			xhr.open('POST', this.form.action, true);
			xhr.setRequestHeader('Content-Type', file.type);
			xhr.setRequestHeader('X-File-Name', file.name);
			xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
			xhr.send(file);

		}

	};

	// Form validation object
	function FormVerify (elem, method, options) {

		if (typeof this[method] === 'function') {
			this[method](elem, options);
		}
		return elem;
		
	};
	FormVerify.prototype = {

		methods: {
			//email: /^(?:(?:"[^"]+")|(?:'[^']+')|(?:[\w!#$%&'*+\-\/=?\^_`{|}~]+))@(?:[\w](?:\-?[\w]+)?\.)*?[\w]+(?:\.[a-z]{2})?\.[a-z]{2,4}$/,
			email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/,
			tel: /^(?:(?:\+?1\s*(?:[\.\-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[\.\-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[\.\-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/,
			url: /^(?:https?:\/\/)?(?:[\w](?:\-?[\w]+)?\.)*?[\w]+(?:\.[a-z]{2})?\.[a-z]{2,4}(\/.+)?$/
		},

		shake: function (elem, options) {

			var plum = elem.closest('div.plum-form'), i = 1;
			if (typeof plum.data('shaking') !== 'undefined') {
				plum.css('left', plum.data('shaking')).removeData('shaking');
			}
			plum.data('shaking', plum.css('left')).stop(true);
			for (; i < 5; i++) {
				plum.animate({ left: '-=15' }, 50).animate({ left: '+=15' }, 50);
			}

		},
		verify: function (elem, options) {

			var plum = this,
			c = elem.data('classes'),
			info = $('<div class="' + c.info + '">').insertAfter(elem);

			elem.bind('blur', function () {

				var valid = true;
				if ('min' in options) {
					valid = valid && this.value.length >= options.min;
				}
				if ('max' in options) {
					valid = valid && this.value.length <= options.max;
				}
				if ('method' in options) {
					valid = 'min' in options && !options.min && !this.value ? true
						: valid && plum.methods[options.method].test(this.value);
				}
				if (typeof options === 'function') {
					valid = !!options.call(this);
				} else if (typeof options === 'string') {
					valid = this.value === options;
				}
				if (!valid) {
					elem.data('invalid', true);
					info.removeClass(c.success).addClass(c.error);
				} else {
					elem.data('invalid', false);
					info.removeClass(c.error).addClass(c.success);
				}

			});

		}

	};

	// jQuery plugin handler
	plum.form = function (options, method) {

		if (typeof options === 'string') {
			return this.each(function () {
				var elem = $(this);
				if (elem.is(':plum')) {
					return new FormVerify(elem, options, method);
				}
				return true;
			});
		}

		// Create a new plum.Form for each matched form
		var forms = $();
		this.each(function () {

			var form, elem = $(this);
			if (this.nodeName.toLowerCase() === 'form') {
				form = this;
				if (!form.plum) {
					form.plum = $();
				}
				form.plum = form.plum.add($(':input:not(:hidden)', this));
			} else {
				form = (elem.closest('form') || elem.find('form'))[0];
				if (!form.plum) {
					form.plum = $();
				}
				form.plum = form.plum.add(elem);
			}
			forms = forms.add(form);

		});
		forms.each(function () {
			return new Form(this, options);
		});

		return this;

	};

	// Add the Form and FormVerify objects to plum
	plum.Form = Form;
	plum.FormVerify = FormVerify;

}(jQuery, plum));
