/**
 * jquery.multisortable.js - v0.2
 * https://github.com/shvetsgroup/jquery.multisortable
 *
 * Author: Ethan Atlakson, Jay Hayes, Gabriel Such, Alexander Shvets
 * Last Revision 3/16/2012
 * multi-selectable, multi-sortable jQuery plugin
 */

!function($) {

	$.fn.multiselectable = function(options) {
		if (!options) {
			options = {}
		}
		options = $.extend({}, $.fn.multiselectable.defaults, options);

		function mouseDown(e) {
			var item = $(this),
				parent = item.parent(),
				myIndex = item.index();

			var prev = parent.find('.multiselectable-previous');
			// If no previous selection found, start selecting from first selected item.
			prev = prev.length ? prev : $(parent.find('.' + options.selectedClass)[0]).addClass('multiselectable-previous');
			var prevIndex = prev.index();

			if (e.ctrlKey || e.metaKey) {
				if (item.hasClass(options.selectedClass)) {
					item.removeClass(options.selectedClass).removeClass('multiselectable-previous')
					if (item.not('.child').length) {
						item.nextUntil(':not(.child)').removeClass(options.selectedClass);
					}
				}
				else {
					parent.find('.multiselectable-previous').removeClass('multiselectable-previous');
					item.addClass(options.selectedClass).addClass('multiselectable-previous')
					if (item.not('.child').length) {
						item.nextUntil(':not(.child)').addClass(options.selectedClass);
					}
				}
			}

			if (e.shiftKey) {
				var last_shift_range = parent.find('.multiselectable-shift');
				last_shift_range.removeClass(options.selectedClass).removeClass('multiselectable-shift');

				var shift_range;
				if (prevIndex < myIndex) {
					shift_range = item.prevUntil('.multiselectable-previous').add(prev).add(item);
				}
				else if (prevIndex > myIndex) {
					shift_range = item.nextUntil('.multiselectable-previous').add(prev).add(item);
				}
				shift_range.addClass(options.selectedClass).addClass('multiselectable-shift');
			}
			else {
				parent.find('.multiselectable-shift').removeClass('multiselectable-shift');
			}

			if (!e.ctrlKey && !e.metaKey && !e.shiftKey) {
				parent.find('.multiselectable-previous').removeClass('multiselectable-previous');
				if (!item.hasClass(options.selectedClass)) {
					parent.find('.' + options.selectedClass).removeClass(options.selectedClass);
					item.addClass(options.selectedClass).addClass('multiselectable-previous');
					if (item.not('.child').length) {
						item.nextUntil(':not(.child)').addClass(options.selectedClass);
					}
				}
			}

			options.mousedown(e, item);
		}

		function click(e) {
			if ( $(this).is('.ui-draggable-dragging') ) {
				return;
			}

			var item = $(this),	parent = item.parent();

			// If item wasn't draged and is not multiselected, it should reset selection for other items.
			if (!e.ctrlKey && !e.metaKey && !e.shiftKey) {
				parent.find('.multiselectable-previous').removeClass('multiselectable-previous');
				parent.find('.' + options.selectedClass).removeClass(options.selectedClass);
				item.addClass(options.selectedClass).addClass('multiselectable-previous');
				if (item.not('.child').length) {
					item.nextUntil(':not(.child)').addClass(options.selectedClass);
				}
			}

			options.click(e, item);
		}

		return this.each(function() {
			var list = $(this);

			if (!list.data('multiselectable')) {
				list.data('multiselectable', true)
					.delegate(options.items, 'mousedown', mouseDown)
					.delegate(options.items, 'click', click)
					.disableSelection();
			}
		})
	};

	$.fn.multiselectable.defaults = {
		click: function(event, elem) {},
		mousedown: function(event, elem) {},
		selectedClass: 'selected',
		items: 'li'
	};


	$.fn.multisortable = function(options) {
		if (!options) {
			options = {}
		}
		var settings = $.extend({}, $.fn.multisortable.defaults, options);

		function regroup(item, list) {
			if (list.find('.' + settings.selectedClass).length > 0) {
				var myIndex = item.data('i');

				var itemsBefore = list.find('.' + settings.selectedClass).filter(function() {
					return $(this).data('i') < myIndex
				}).css({
						position: '',
						width: '',
						left: '',
						top: '',
						zIndex: ''
					});

				item.before(itemsBefore);

				var itemsAfter = list.find('.' + settings.selectedClass).filter(function() {
					return $(this).data('i') > myIndex
				}).css({
						position: '',
						width: '',
						left: '',
						top: '',
						zIndex: ''
					});

				item.after(itemsAfter);

				setTimeout(function() {
					itemsAfter.add(itemsBefore).addClass(settings.selectedClass);
				}, 0);
			}
		}

		return this.each(function() {
			var list = $(this);

			//enable multi-selection
			list.multiselectable({
				selectedClass: settings.selectedClass,
				click: settings.click,
				items: settings.items,
				mousedown: settings.mousedown
			});

			//enable sorting
			options.cancel = settings.items + ':not(.' + settings.selectedClass + ')';
			options.placeholder = settings.placeholder;
			options.start = function(event, ui) {
				if (ui.item.hasClass(settings.selectedClass)) {
					var parent = ui.item.parent();

					//assign indexes to all selected items
					parent.find('.' + settings.selectedClass).each(function(i) {
						$(this).data('i', i);
					});

					// adjust placeholder size to be size of items
					var height = parent.find('.' + settings.selectedClass).length * ui.item.outerHeight();
					ui.placeholder.height(height);
				}

				settings.start(event, ui);
			};

			options.stop = function(event, ui) {
				regroup(ui.item, ui.item.parent());
				settings.stop(event, ui);
			};

			options.sort = function(event, ui) {
				var parent = ui.item.parent(),
					myIndex = ui.item.data('i'),
					top = parseInt(ui.item.css('top').replace('px', '')),
					left = parseInt(ui.item.css('left').replace('px', ''));

				// fix to keep compatibility using prototype.js and jquery together
				$.fn.reverse = Array.prototype._reverse || Array.prototype.reverse

				var height = 0;
				$('.' + settings.selectedClass, parent).filter(function() {
					return $(this).data('i') < myIndex;
				}).reverse().each(function() {
						height += $(this).outerHeight();
						$(this).css({
							left: left,
							top: top - height,
							position: 'absolute',
							zIndex: 1000,
							width: ui.item.width()
						})
					});

				height = ui.item.outerHeight();
				$('.' + settings.selectedClass, parent).filter(function() {
					return $(this).data('i') > myIndex;
				}).each(function() {
						var item = $(this);
						item.css({
							left: left,
							top: top + height,
							position: 'absolute',
							zIndex: 1000,
							width: ui.item.width()
						});

						height += item.outerHeight();
					});

				settings.sort(event, ui);
			};

			options.receive = function(event, ui) {
				regroup(ui.item, ui.sender);
				settings.receive(event, ui);
			};

			list.sortable(options).disableSelection();
		})
	};

	$.fn.multisortable.defaults = {
		start: function(event, ui) {},
		stop: function(event, ui) {},
		sort: function(event, ui) {},
		receive: function(event, ui) {},
		click: function(event, elem) {},
		mousedown: function(event, elem) {},
		selectedClass: 'selected',
		placeholder: 'placeholder',
		items: 'li'
	};

}(jQuery);
