/*
 * jQuery UI Swappable 0.9.5
 *
 * Copyright (c) 2010 Vadim Kiryukhin
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * Based on jquery.ui.sortable.js
 *
 * Depends:
 *		jquery.ui.core.js
 *		jquery.ui.mouse.js
 *		jquery.ui.widget.js
 *  	jquery.ui.sortable.js
 *
 * Usage:
 *
 *		Option's specific:
 *
 *		1. Always set option "cursorAt: {top: -nnn}" as a Negative Integer.
 *		2. Always set option "items" with items' class name, not element or filter.
 *
 *		Example:
 *			
 *			<ul id="foo">
 *				<li class="bar"><li>
 *				<li class="bar"><li>
 *				<li class="bar"><li>
 *			</ul>
 *			
 *			$("#foo").swappable({
 *				items:'.bar', // Mandatory option, class only.
 *				cursorAt: {top:-20}, // MUST be set to negative. Default doesn't work!
 *			});
 */

var coordClickRelativeToItemX;
var coordClickRelativeToItemY;

(function($) {

    $.widget("ui.swappable", $.ui.sortable, {

        _mouseStart: function(event, overrideHandle, noActivation) {

            var o = this.options, self = this;
            o.helper = "original",

		this.currentContainer = this;

            //Create and append the visible helper
            this.helper = this._createHelper(event);

            //Cache the helper size
            this._cacheHelperProportions();

            /*
            * - Position generation -
            * This block generates everything position related - it's the core of draggables.
            */

            //Cache the margins of the original element
            this._cacheMargins();

            //Get the next scrolling parent
            this.scrollParent = this.helper.scrollParent();

            //The element's absolute position on the page minus margins
            this.offset = this.currentItem.offset();
            this.offset = {
                top: this.offset.top - this.margins.top,
                left: this.offset.left - this.margins.left
            };

            // Only after we got the offset, we can change the helper's position to absolute
            // TODO: Still need to figure out a way to make relative sorting possible
            this.helper.css("position", "absolute");
            this.cssPosition = this.helper.css("position");

            coordClickRelativeToItemX = event.pageX - this.offset.left;
            coordClickRelativeToItemY = event.pageY - this.offset.top;

            $.extend(this.offset, {
                click: { //Where the click happened, relative to the element
                    left: event.pageX - this.offset.left,
                    top: event.pageY - this.offset.top
                },
                parent: this._getParentOffset(),
                relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
            });

            //Generate the original position
            this.originalPosition = this._generatePosition(event);
            this.originalPageX = event.pageX;
            this.originalPageY = event.pageY;

            //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
            (o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));

            //Cache the former DOM position
            this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };

            //If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
            if (this.helper[0] != this.currentItem[0]) {
                this.currentItem.hide();
            }

            //Create the placeholder
            this._createPlaceholder();

            //Set a containment if given in the options
            if (o.containment)
                this._setContainment();

            if (o.cursor) { // cursor option
                if ($('body').css("cursor")) this._storedCursor = $('body').css("cursor");
                $('body').css("cursor", o.cursor);
            }

            if (o.opacity) { // opacity option
                if (this.helper.css("opacity")) this._storedOpacity = this.helper.css("opacity");
                this.helper.css("opacity", o.opacity);
            }

            if (o.zIndex) { // zIndex option
                if (this.helper.css("zIndex")) this._storedZIndex = this.helper.css("zIndex");
                this.helper.css("zIndex", o.zIndex);
            }

            //Prepare scrolling
            if (this.scrollParent[0] != document && this.scrollParent[0].tagName != 'HTML')
                this.overflowOffset = this.scrollParent.offset();

            //Call callbacks
            this._trigger("start", event, this._uiHash());

            //Recache the helper size
            if (!this._preserveHelperProportions)
                this._cacheHelperProportions();


            //Post 'activate' events to possible containers
            if (!noActivation) {
                for (var i = this.containers.length - 1; i >= 0; i--) { this.containers[i]._trigger("activate", event, self._uiHash(this)); }
            }

            //Prepare possible droppables
            if ($.ui.ddmanager)
                $.ui.ddmanager.current = this;

            if ($.ui.ddmanager && !o.dropBehaviour)
                $.ui.ddmanager.prepareOffsets(this, event);

            this.dragging = true;

            this.helper.addClass("ui-sortable-helper");
            this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
            return true;

        },
        _mouseStop: function(event, noPropagation) {

            if (!event) return;

            var o = this.options;
            var target = event.target;

            var itemPassive = $(target).closest(o.items);
            var itemActive = this.currentItem.closest(o.items);
            var itemPlaceholder = $(itemActive).next();

            $(itemPassive).after(itemActive);
            $(itemPlaceholder).after(itemPassive);

            //If we are using droppables, inform the manager about the drop
            if ($.ui.ddmanager && !this.options.dropBehaviour)
                $.ui.ddmanager.drop(this, event);

            if (this.options.revert) {
                var self = this;
                var cur = self.placeholder.offset();

                self.reverting = true;

                $(this.helper).animate({
                    left: cur.left - this.offset.parent.left - self.margins.left + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollLeft),
                    top: cur.top - this.offset.parent.top - self.margins.top + (this.offsetParent[0] == document.body ? 0 : this.offsetParent[0].scrollTop)
                }, parseInt(this.options.revert, 10) || 500, function() {
                    self._clear(event);
                });
            } else {
                this._clear(event, noPropagation);
            }

            return false;

        },

        _clear: function(event, noPropagation) {

            this.reverting = false;
            // We delay all events that have to be triggered to after the point where the placeholder has been removed and
            // everything else normalized again
            var delayedTriggers = [], self = this;

            this._noFinalSort = null;

            if (this.helper[0] == this.currentItem[0]) {

                for (var i in this._storedCSS) {
                    if (this._storedCSS[i] == 'auto' || this._storedCSS[i] == 'static') this._storedCSS[i] = '';
                }
                this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
            } else {
                this.currentItem.show();
            }

            if (this.fromOutside && !noPropagation) delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
            if ((this.fromOutside || this.domPosition.prev != this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent != this.currentItem.parent()[0]) && !noPropagation) delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
            if (!$.ui.contains(this.element[0], this.currentItem[0])) { //Node was moved out of the current element
                if (!noPropagation) delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
                for (var i = this.containers.length - 1; i >= 0; i--) {
                    if ($.ui.contains(this.containers[i].element[0], this.currentItem[0]) && !noPropagation) {
                        delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
                        delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
                    }
                };
            };

            //Post events to containers
            for (var i = this.containers.length - 1; i >= 0; i--) {
                if (!noPropagation) delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
                if (this.containers[i].containerCache.over) {
                    delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); }; }).call(this, this.containers[i]));
                    this.containers[i].containerCache.over = 0;
                }
            }

            //Do what was originally in plugins
            if (this._storedCursor) $('body').css("cursor", this._storedCursor); //Reset cursor
            if (this._storedOpacity) this.helper.css("opacity", this._storedOpacity); //Reset opacity
            if (this._storedZIndex) this.helper.css("zIndex", this._storedZIndex == 'auto' ? '' : this._storedZIndex); //Reset z-index

            this.dragging = false;
            if (this.cancelHelperRemoval) {
                if (!noPropagation) {
                    this._trigger("beforeStop", event, this._uiHash());
                    for (var i = 0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
                    this._trigger("stop", event, this._uiHash());
                }
                return false;
            }

            if (!noPropagation) this._trigger("beforeStop", event, this._uiHash());

            //$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
            this.placeholder[0].parentNode.removeChild(this.placeholder[0]);

            if (this.helper[0] != this.currentItem[0]) this.helper.remove(); this.helper = null;

            if (!noPropagation) {
                for (var i = 0; i < delayedTriggers.length; i++) { delayedTriggers[i].call(this, event); }; //Trigger all delayed events
                this._trigger("stop", event, this._uiHash());
            }

            this.fromOutside = false;
            return true;

        }

    });

    $.extend($.ui.swappable, {
        version: "0.9.5"
    });

})(jQuery);
