(function($) {
	$.fn.placeholder = function(options) {
		
		var defaults = {
			// Placeholder
			placeholderValue: function(input) { return $(input).val(); },
			placeholderClass: 'placeholder',
			// Submit behaviour
			zeroOnSubmit: true,
			// Delete button
			deletePlaceholder: false,
			deletePlaceholderImage: 'delete.jpg',
			// Error button
			error: false,
			errorValue: function(input) { return false; },
			errorImage: 'error.jpg',
			// Delete/error sizes
			imageSize: [16, 16],
			imageOffset: [3, 2],
			// Save button
			saveButton: false,
			saveButtonOffset: [10, -1]
		}
		var options = $.extend(defaults, options);
		
		function placeholderFocus() {
			input = $(this);
			input.removeClass(options.placeholderClass);
			if (input.val() == input.data('placeholder'))
				input.val('');
		}
		
		function placeholderBlur() {
			input = $(this);
			if (!input.val() || input.val() == input.data('placeholder'))
				input.val(input.data('placeholder')).addClass(options.placeholderClass);
			else input.removeClass(options.placeholderClass);
		}
		
		function placeholderDouble() {
			input = $(this);
			if (!input.val() || input.val() == input.data('placeholder')) {
				input.val(input.data('placeholder')).removeClass(options.placeholderClass);
				return false;
			}
		}
		
		function placeholderKeyUp(e) {
			if (e.keyCode == 27)
				$(this).val('').blur();
		}
		
		// Remove any placeholder before submit
		options.zeroOnSubmit && this.parents('form').submit(function() {
			$('.placeholder', this).val('');
		});
		
		// Generate save button
		options.saveButton && this.parents('form').each(function() {
			$('<div class="placeholder_save_container"></div>')
			.css({
				position: 'absolute',
				display: 'none'
			})
			.append($('<input class="placeholder_save_button" type="submit" value="'
				+ (options.saveButton === true ? 'save' : optins.saveButton)
				+ '"/>'))
			.appendTo($(this));
		});
		
		// Generate delete/error button
		options.deletePlaceholder && this.parents('form').each(function() {
			$this = $(this);
			$('<div class="placeholder_button_container"></div>')
			.css({
				position: 'absolute',
				display: 'none',
				width: options.imageSize[0],
				height: options.imageSize[1],
				background: 'url('+options.deletePlaceholderImage+') no-repeat',
				cursor: 'pointer'
			})
			.click(function() {
				$this.data('placeholder', '').val('');
			})
			.appendTo($this);
		});
		
		function showButton() {
			$this = $(this).addClass('placeholder_selected');
			form = $this.parents('form:first');
			off = $this.offset();
			off_form = form.offset();
			off.top -= off_form.top;
			off.left -= off_form.left;
			if (options.saveButton) $('.placeholder_save_container', form)
			.css({
				top: off.top + options.saveButtonOffset[1],
				left: off.left + $this.width() + options.saveButtonOffset[0]
			})
			.fadeIn(300);
			if (options.deletePlaceholder) {
				b = $('.placeholder_button_container', form); b
				.css({
					top: off.top + options.imageOffset[1],
					left: off.left + $this.width() + options.imageOffset[0] - b.width()
				})
				.fadeIn(300);	
			}
		}
		
		function hideButton() {
			$this = $(this).removeClass('placeholder_selected');
			$('.placeholder_save_container, .placeholder_button_container', $this.parents('form:first')).fadeOut(300);
		}
		
		function hideError() {
			$(this).removeClass('placeholder_error');
		}
		
		// Apply placeholder logic
		return this.each(function() {
			input = $(this);

			val = options.placeholderValue;
			if (!val) val = input.val();
			else if (typeof val == 'function') val = val(this);
			
			(options.saveButton || options.deletePlaceholder) && input.focus(showButton.debounce(200)).blur(hideButton);
			
			input
				.data('placeholder', val)
				.focus(placeholderFocus)
				.blur(placeholderBlur)
				.dblclick(placeholderDouble)
				.keyup(placeholderKeyUp);
			if (val == input.val()) 
				input.addClass(options.placeholderClass);
			if (options.error && (err = options.errorValue(input))) input
				.data('placeholder_error', err)
				.addClass('placeholder_error')
				.change(hideError);
		});
	};
})(jQuery);
