/*! 
* jquery.event.drag - v 2.2
* Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
* Open Source MIT License - http://threedubmedia.com/code/license
*/
// Created: 2008-06-04 
// Updated: 2012-05-21
// REQUIRES: jquery 1.7.x

; (function ($) {

    // add the jquery instance method
    $.fn.drag = function (str, arg, opts) {
        // figure out the event type
        var type = typeof str == "string" ? str : "",
        // figure out the event handler...
	fn = $.isFunction(str) ? str : $.isFunction(arg) ? arg : null;
        // fix the event type
        if (type.indexOf("drag") !== 0)
            type = "drag" + type;
        // were options passed
        opts = (str == fn ? arg : opts) || {};
        // trigger or bind event handler
        return fn ? this.bind(type, opts, fn) : this.trigger(type);
    };

    // local refs (increase compression)
    var $event = $.event,
$special = $event.special,
    // configure the drag special event 
drag = $special.drag = {

    // these are the default settings
    defaults: {
        which: 1, // mouse button pressed to start drag sequence
        distance: 0, // distance dragged before dragstart
        not: ':input', // selector to suppress dragging on target elements
        handle: null, // selector to match handle target elements
        relative: false, // true to use "position", false to use "offset"
        drop: true, // false to suppress drop events, true or selector to allow
        click: false // false to suppress click events after dragend (no proxy)
    },

    // the key name for stored drag data
    datakey: "dragdata",

    // prevent bubbling for better performance
    noBubble: true,

    // count bound related events
    add: function (obj) {
        // read the interaction data
        var data = $.data(this, drag.datakey),
        // read any passed options 
		opts = obj.data || {};
        // count another realted event
        data.related += 1;
        // extend data options bound with this event
        // don't iterate "opts" in case it is a node 
        $.each(drag.defaults, function (key, def) {
            if (opts[key] !== undefined)
                data[key] = opts[key];
        });
    },

    // forget unbound related events
    remove: function () {
        $.data(this, drag.datakey).related -= 1;
    },

    // configure interaction, capture settings
    setup: function () {
        // check for related events
        if ($.data(this, drag.datakey))
            return;
        // initialize the drag data with copied defaults
        var data = $.extend({ related: 0 }, drag.defaults);
        // store the interaction data
        $.data(this, drag.datakey, data);
        // bind the mousedown event, which starts drag interactions
        $event.add(this, "touchstart mousedown", drag.init, data);
        // prevent image dragging in IE...
        if (this.attachEvent)
            this.attachEvent("ondragstart", drag.dontstart);
    },

    // destroy configured interaction
    teardown: function () {
        var data = $.data(this, drag.datakey) || {};
        // check for related events
        if (data.related)
            return;
        // remove the stored data
        $.removeData(this, drag.datakey);
        // remove the mousedown event
        $event.remove(this, "touchstart mousedown", drag.init);
        // enable text selection
        drag.textselect(true);
        // un-prevent image dragging in IE...
        if (this.detachEvent)
            this.detachEvent("ondragstart", drag.dontstart);
    },

    // initialize the interaction
    init: function (event) {
        // sorry, only one touch at a time
        if (drag.touched)
            return;
        // the drag/drop interaction data
        var dd = event.data, results;
        // check the which directive
        if (event.which != 0 && dd.which > 0 && event.which != dd.which)
            return;
        // check for suppressed selector
        if ($(event.target).is(dd.not))
            return;
        // check for handle selector
        if (dd.handle && !$(event.target).closest(dd.handle, event.currentTarget).length)
            return;

        drag.touched = event.type == 'touchstart' ? this : null;
        dd.propagates = 1;
        dd.mousedown = this;
        dd.interactions = [drag.interaction(this, dd)];
        dd.target = event.target;
        dd.pageX = event.pageX;
        dd.pageY = event.pageY;
        dd.dragging = null;
        // handle draginit event... 
        results = drag.hijack(event, "draginit", dd);
        // early cancel
        if (!dd.propagates)
            return;
        // flatten the result set
        results = drag.flatten(results);
        // insert new interaction elements
        if (results && results.length) {
            dd.interactions = [];
            $.each(results, function () {
                dd.interactions.push(drag.interaction(this, dd));
            });
        }
        // remember how many interactions are propagating
        dd.propagates = dd.interactions.length;
        // locate and init the drop targets
        if (dd.drop !== false && $special.drop)
            $special.drop.handler(event, dd);
        // disable text selection
        drag.textselect(false);
        // bind additional events...
        if (drag.touched)
            $event.add(drag.touched, "touchmove touchend", drag.handler, dd);
        else
            $event.add(document, "mousemove mouseup", drag.handler, dd);
        // helps prevent text selection or scrolling
        if (!drag.touched || dd.live)
            return false;
    },

    // returns an interaction object
    interaction: function (elem, dd) {
        var offset = $(elem)[dd.relative ? "position" : "offset"]() || { top: 0, left: 0 };
        return {
            drag: elem,
            callback: new drag.callback(),
            droppable: [],
            offset: offset
        };
    },

    // handle drag-releatd DOM events
    handler: function (event) {
        // read the data before hijacking anything
        var dd = event.data;
        // handle various events
        switch (event.type) {
            // mousemove, check distance, start dragging 
            case !dd.dragging && 'touchmove':
                event.preventDefault();
            case !dd.dragging && 'mousemove':
                //  drag tolerance, x≤ + y≤ = distance≤
                if (Math.pow(event.pageX - dd.pageX, 2) + Math.pow(event.pageY - dd.pageY, 2) < Math.pow(dd.distance, 2))
                    break; // distance tolerance not reached
                event.target = dd.target; // force target from "mousedown" event (fix distance issue)
                drag.hijack(event, "dragstart", dd); // trigger "dragstart"
                if (dd.propagates) // "dragstart" not rejected
                    dd.dragging = true; // activate interaction
                // mousemove, dragging
            case 'touchmove':
                event.preventDefault();
            case 'mousemove':
                if (dd.dragging) {
                    // trigger "drag"		
                    drag.hijack(event, "drag", dd);
                    if (dd.propagates) {
                        // manage drop events
                        if (dd.drop !== false && $special.drop)
                            $special.drop.handler(event, dd); // "dropstart", "dropend"							
                        break; // "drag" not rejected, stop		
                    }
                    event.type = "mouseup"; // helps "drop" handler behave
                }
                // mouseup, stop dragging
            case 'touchend':
            case 'mouseup':
            default:
                if (drag.touched)
                    $event.remove(drag.touched, "touchmove touchend", drag.handler); // remove touch events
                else
                    $event.remove(document, "mousemove mouseup", drag.handler); // remove page events	
                if (dd.dragging) {
                    if (dd.drop !== false && $special.drop)
                        $special.drop.handler(event, dd); // "drop"
                    drag.hijack(event, "dragend", dd); // trigger "dragend"	
                }
                drag.textselect(true); // enable text selection
                // if suppressing click events...
                if (dd.click === false && dd.dragging)
                    $.data(dd.mousedown, "suppress.click", new Date().getTime() + 5);
                dd.dragging = drag.touched = false; // deactivate element	
                break;
        }
    },

    // re-use event object for custom events
    hijack: function (event, type, dd, x, elem) {
        // not configured
        if (!dd)
            return;
        // remember the original event and type
        var orig = { event: event.originalEvent, type: event.type },
        // is the event drag related or drog related?
		mode = type.indexOf("drop") ? "drag" : "drop",
        // iteration vars
		result, i = x || 0, ia, $elems, callback,
		len = !isNaN(x) ? x : dd.interactions.length;
        // modify the event type
        event.type = type;
        // remove the original event
        event.originalEvent = null;
        // initialize the results
        dd.results = [];
        // handle each interacted element
        do if (ia = dd.interactions[i]) {
            // validate the interaction
            if (type !== "dragend" && ia.cancelled)
                continue;
            // set the dragdrop properties on the event object
            callback = drag.properties(event, dd, ia);
            // prepare for more results
            ia.results = [];
            // handle each element
            $(elem || ia[mode] || dd.droppable).each(function (p, subject) {
                // identify drag or drop targets individually
                callback.target = subject;
                // force propagtion of the custom event
                event.isPropagationStopped = function () { return false; };
                // handle the event	
                result = subject ? $event.dispatch.call(subject, event, callback) : null;
                // stop the drag interaction for this element
                if (result === false) {
                    if (mode == "drag") {
                        ia.cancelled = true;
                        dd.propagates -= 1;
                    }
                    if (type == "drop") {
                        ia[mode][p] = null;
                    }
                }
                // assign any dropinit elements
                else if (type == "dropinit")
                    ia.droppable.push(drag.element(result) || subject);
                // accept a returned proxy element 
                if (type == "dragstart")
                    ia.proxy = $(drag.element(result) || ia.drag)[0];
                // remember this result	
                ia.results.push(result);
                // forget the event result, for recycling
                delete event.result;
                // break on cancelled handler
                if (type !== "dropinit")
                    return result;
            });
            // flatten the results	
            dd.results[i] = drag.flatten(ia.results);
            // accept a set of valid drop targets
            if (type == "dropinit")
                ia.droppable = drag.flatten(ia.droppable);
            // locate drop targets
            if (type == "dragstart" && !ia.cancelled)
                callback.update();
        }
        while (++i < len)
        // restore the original event & type
        event.type = orig.type;
        event.originalEvent = orig.event;
        // return all handler results
        return drag.flatten(dd.results);
    },

    // extend the callback object with drag/drop properties...
    properties: function (event, dd, ia) {
        var obj = ia.callback;
        // elements
        obj.drag = ia.drag;
        obj.proxy = ia.proxy || ia.drag;
        // starting mouse position
        obj.startX = dd.pageX;
        obj.startY = dd.pageY;
        // current distance dragged
        obj.deltaX = event.pageX - dd.pageX;
        obj.deltaY = event.pageY - dd.pageY;
        // original element position
        obj.originalX = ia.offset.left;
        obj.originalY = ia.offset.top;
        // adjusted element position
        obj.offsetX = obj.originalX + obj.deltaX;
        obj.offsetY = obj.originalY + obj.deltaY;
        // assign the drop targets information
        obj.drop = drag.flatten((ia.drop || []).slice());
        obj.available = drag.flatten((ia.droppable || []).slice());
        return obj;
    },

    // determine is the argument is an element or jquery instance
    element: function (arg) {
        if (arg && (arg.jquery || arg.nodeType == 1))
            return arg;
    },

    // flatten nested jquery objects and arrays into a single dimension array
    flatten: function (arr) {
        return $.map(arr, function (member) {
            return member && member.jquery ? $.makeArray(member) :
				member && member.length ? drag.flatten(member) : member;
        });
    },

    // toggles text selection attributes ON (true) or OFF (false)
    textselect: function (bool) {
        $(document)[bool ? "unbind" : "bind"]("selectstart", drag.dontstart)
			.css("MozUserSelect", bool ? "" : "none");
        // .attr("unselectable", bool ? "off" : "on" )
        document.unselectable = bool ? "off" : "on";
    },

    // suppress "selectstart" and "ondragstart" events
    dontstart: function () {
        return false;
    },

    // a callback instance contructor
    callback: function () { }

};

    // callback methods
    drag.callback.prototype = {
        update: function () {
            if ($special.drop && this.available.length)
                $.each(this.available, function (i) {
                    $special.drop.locate(this, i);
                });
        }
    };

    // patch $.event.$dispatch to allow suppressing clicks
    var $dispatch = $event.dispatch;
    $event.dispatch = function (event) {
        if ($.data(this, "suppress." + event.type) - new Date().getTime() > 0) {
            $.removeData(this, "suppress." + event.type);
            return;
        }
        return $dispatch.apply(this, arguments);
    };

    // event fix hooks for touch events...
    var touchHooks =
$event.fixHooks.touchstart =
$event.fixHooks.touchmove =
$event.fixHooks.touchend =
$event.fixHooks.touchcancel = {
    props: "clientX clientY pageX pageY screenX screenY".split(" "),
    filter: function (event, orig) {
        if (orig) {
            var touched = (orig.touches && orig.touches[0])
				|| (orig.changedTouches && orig.changedTouches[0])
				|| null;
            // iOS webkit: touchstart, touchmove, touchend
            if (touched)
                $.each(touchHooks.props, function (i, prop) {
                    event[prop] = touched[prop];
                });
        }
        return event;
    }
};

    // share the same special event configuration with related events...
    $special.draginit = $special.dragstart = $special.dragend = drag;

})(jQuery);