# Marijn's Drag, and Drag&Drop implementation that I can understand
# Concepts: 
#	- Have one or more registered drop regions each with its own handling functions
#	- Pass the DOM element one is over to the drop region handling functions
#   - Use generic functionality for the actual move event hendling
#	- Handle the core of the D&D processing internally
#	- Easily able to instanciate multiple DND instances

# *** Actual core DND code ***

var DND = function(el, f1, f2, f3, dragself)
{
    this.elt=el;
    this._onDragDropStart=f1;
    this._onDragDropEnd=f2;
    this._onDragDropNoTarget=f3;
    this._drag = new DragThing(this.elt, Delegate.create(this, this._onDragStart),
        				Delegate.create(this, this._onDrag), Delegate.create(this, this._onDragEnd));
    this._drag.sensitivity=DND.MOUSE_SENSITIVITY;
    this._dragself = dragself;
    if(dragself) return;
    DND.RegisterDropTarget(this.elt, null, null, Delegate.Null);
};
DND.MOUSE_SENSITIVITY = 3;
DND.prototype = {
    elt: null,
    userdata: null,
    _drag: null,
    _clone: null,
    _onDragDropStart: null,
    _onDragDropEnd: null,
    _currTarget: null,
    _cursorDiv: null,
    dispose: function()
    {
        this._drag.dispose();
        this.userdata=null;
        this.elt=null;
    },
    _onDragStart: function(jc, o)
    {
		dbg('DND onDragStart');		
        var el=o.overElement, 
        oo = { fromElement: el, data: null };
        this._onDragDropStart(this, o);
        DND._UpdateDropTargetRegions();
        jc.userdata = { startX: event.clientX-el.offsetWidth/2, startY: event.clientY-el.offsetHeight/2,
            				fromElement: el, dragData: o.data};

        this._currTarget=null;
        event.cancelBubble=true;
 
 		this._cursorDiv=document.createElement("DIV");
        document.body.appendChild(this._cursorDiv);
        var s=this._cursorDiv.style;
        s.cursor="pointer";
        s.position="absolute";
        s.height="20px";
        s.width="20px";
        s.zIndex=1000;
    },
    _onDrag: function(jc, o)
    {
        var d=jc.userdata;
		var	oo = { fromElement: d.fromElement, toElement: null, data: d.dragData, canDrop: false, overElement: o.overElement };
        this._doDragOver(o.overElement, oo);
    },
    _onDragEnd: function(jc)
    {
		dbg('DND onDragEnd');		
        document.body.style.cursor = "auto";
        var d=jc.userdata, targets = DND._targets, e;
        var oo = {fromElement: d.fromElement, toElement: null, data: d.dragData, success: false};
        this._setNewCurrTarget(null, "onDragDrop", oo);
        document.body.removeChild(this._cursorDiv);
        this._onDragDropEnd(this, oo);
        d.fromElement=null;
        d.toElement=null;
        event.cancelBubble=true;
        event.returnValue=false;
    },
    _doDragOver: function(el, o)
    {
        var targets=DND._targets, p;
        o.toElement=null;
        var x=event.clientX, y=event.clientY, s=this._cursorDiv.style;


        if (!this._currTarget) {
            this._onDragDropNoTarget(this, o);
            s.left=px(x-10);
            s.top=px(y-10);
        } else if (!this._dragself && this._currTarget.element == this.elt && this._currTarget.region.containsPoint(x, y)) {
            this._onDragDropNoTarget(this, o);
            s.left=px(x-10);
            s.top=px(y-10);
            return;
        }

        for (var i=targets.length-1; i>=0; i--) {
            p=targets[i];
            var r=p.region;
#            dbg('CHECK (x='+x+' y='+y+') R' +targets[i].element.id+' top='+r.top+' left='+r.left+' right='+r.right+' bot='+r.bottom)
            if (p.region.containsPoint(x, y)) {
#            	dbg('CHECK YES POINT')
                if (this._currTarget!=p) {
                    this._setNewCurrTarget(p, "onDragOut", o);
                    o.toElement=p.element;
                    p["onDragOver"](this, o);
                    s.left="0px";
                    s.top="0px";
                    document.body.style.cursor="pointer";
                    this._cursorDiv.style.cursor="pointer";
                    return;
                }
                p["onDragOver"](this, o);
                return;
            }else{
#            	dbg('CHECK NO POINT')
            }
        }

        if (this._currTarget) {
            document.body.style.cursor="no-drop";
            this._cursorDiv.style.cursor="no-drop";
        }
        this._setNewCurrTarget(null, "onDragOut", o)
    },
    _setNewCurrTarget: function(target, txt, o)
    {
        if (this._currTarget) {
            o.toElement=this._currTarget.element;
            this._currTarget[txt](this, o);
        }
        this._currTarget=target;
    }
};
DND._targets = [];
DND.RegisterDropTarget = function(el, f1, f2, f3)
{
    if (f1 == null || f1 === undefined) f1=Delegate.Null;
    if (f2 == null || f2 === undefined) f2=Delegate.Null;
    DND._targets.push({element: el, onDragOver: f1, onDragOut: f2, onDragDrop: f3, region: null})
};

DND._UpdateDropTargetRegions = function()
{
 	var targets=DND._targets;
    for (var i=targets.length-1; i>=0; i--) {
    	targets[i].region = get_location(targets[i].element);
    	var r=targets[i].region;
#    	dbg('*** REGION: '+targets[i].element.id+' top='+r.top+' left='+r.left+' right='+r.right+' bot='+r.bottom)
    }
};
DND.UnRegisterDropTarget = function(el)
{
    var targets = DND._targets, p;
    for (var i = targets.length - 1; i>=0; i--) {
        p = targets[i];
        if (p.element == el) {
            p.element = null;
            p.removeAt(i);
        }
    }
};


# *** Generic thing dragging library ***
var DragThing = function(el, f1, f2, f3)
{
    this._elt=el;
    this._elt_parent=el.parentNode;
    this._onDragStart=f1;
    this._onDrag=f2;
    this._onDragStop=f3;
    this._bIsMoving=false;
    this._onMouseDownHandler=Delegate.create(this, this._onMouseDown);
    this._onMouseUpHandler=Delegate.create(this, this._onMouseUp);
    this._onMouseMoveHandler=Delegate.create(this, this._onMouseMove);
	xadd_event(this._elt,"mousedown", this._onMouseDownHandler);
};
DragThing.prototype = {
    sensitivity: 0,
    _elt: null,
    _initMouseX: 0,
    _initMouseY: 0,
    _onMouseDownHandler: null,
    _onMouseUpHandler: null,
    _onMouseMoveHandler: null,
    _onDragStart: null,
    _onDrag: null,
    _onDragStop: null,
    _bIsMoving: false,
    _invoked: false,
    enabled: true,
    userdata: null,
    dispose: function()
    {
        xremove_event(this._elt,"mousedown", this._onMouseDownHandler);
        this._elt=null;
        this._elt_parent=null;
        this._onMouseDownHandler=null;
        this._onMouseUpHandler=null;
        this._onMouseMoveHandler=null;
        this._onDragStart=null;
        this._onDrag=null;
        this._onDragStop=null;
        this.userdata=null
    },
    _onMouseDown: function()
    {
#		dbg('DRAG onmousedown enabled='+this.enabled+' moving='+this._bIsMoving)
        if (!this.enabled) return;
        var e=window.event;
        var DOWN=(Browser.isIE&&isVersion<=10?1:0);

        if (e.button===DOWN) if (!this._bIsMoving) {

			if(isMac&&e.ctrlKey) return;
#	        dbg('fns Drag.start ********** isMac='+isMac+'ctl='+e.ctrlKey);

            this._invoked=false;
            this._bIsMoving=true;
			xadd_event(this._elt,"mouseup", this._onMouseUpHandler);
#			Slightly evil hack to use parentNode here but it means "drag pickup" can be made more reliable
#			at the edges of draggable things and mostly it should be just fine
			xadd_event(this._elt_parent,"mousemove", this._onMouseMoveHandler);
            this._initMouseX=e.clientX;
            this._initMouseY=e.clientY;
            e.returnValue=false;

            if (this.sensitivity <= 0) {
				capture_events(this._elt);
	            this._onDragStart(this, { x: 0, y: 0, overElement: e.srcElement});
                this._invoked=true
            } else{
            	this.startElt= Browser.isFF?e.target:e.srcElement||e.currentTarget;
            }

			selection_clear();
        } else {
        	if (this._invoked)
        		this._onMouseUp()
        }
    },
    _onMouseUp: function()
    {
#		dbg('DRAG onmouseup')
        if (!this.enabled) return;
        this._bIsMoving=false;
		capture_release(this._elt);
        xremove_event(this._elt,"mouseup", this._onMouseUpHandler);
        xremove_event(this._elt_parent,"mousemove", this._onMouseMoveHandler);
        var e=window.event, x=e.clientX, y=e.clientY;

        if (this._invoked) this._onDragStop(this, {
            x: this._initMouseX-x,
            y: this._initMouseY-y,
            overElement: Browser.isFF ? event._data || this._elt : document.elementFromPoint(x, y)
        });
        this._invoked = false
    },
    _onMouseMove: function()
    {
        if (!this.enabled) return;
        var e=window.event, DOWN=(Browser.isIE&&isVersion<=10?1:0);

        if (e.button !== DOWN) {this._onMouseUp(); return;}
        var x = e.clientX, y = e.clientY;

        if (!this._invoked) {
            if (Math.abs(this._initMouseX - x + (this._initMouseY - y)) > this.sensitivity) {
                selection_clear();
                capture_events(this._elt);
                this._onDragStart(this, {x:0, y:0, overElement:this.startElt});
                this._invoked = true;
                this.foobar = true;
            }
#            dbg('*** QQQ '+(this._invoked?'I':'N')+'')
        } else {
#        	if(event._data) dbg('*** QQQ '+(this._invoked?'I':'N')+' event._data: '+el_info2(event._data)+el_info2(event._data.parentNode)+el_info2(event._data.parentNode.parentNode));
#        	if(event.target) dbg('*** QQQ '+(this._invoked?'I':'N')+' event.target: '+el_info2(event.target)+el_info2(event.target.parentNode)+el_info2(event.target.parentNode.parentNode));
#        	dbg('*** QQQ '+(this._invoked?'I':'N')+' this._elt: '+el_info2(this._elt)+el_info2(this._elt.parentNode)+el_info2(this._elt.parentNode.parentNode));
        	this._onDrag(this, {x:this._initMouseX - x, y: this._initMouseY - y,
            						overElement: Browser.isFF && isVersion<4 ? event._data || this._elt : document.elementFromPoint(x, y)});
         }
    }
};




||ifdef||_junk_||
var x;
function init(){
	new DragThing(dge('foo'), _onDragStart, _onDrag, _onDragEnd);
	new DragThing(dge('foo'), _onDragStart2, _onDrag2, _onDragEnd2);
    DND.RegisterDropTarget(dge("foo2"), _folderListOnDragOver, _folderListOnDragOut, _folderListOnDragDrop);
	new DND(dge('foo'), _msgListOnDragStart, _msgListOnDragEnd, _msgListNoTarget);
}

# *** DOM node dragger actual mover make more genereric and replace dom-drag.js... ***
function _onDragStart(){
	dbg("CUSTOM dragstart");
	x=dge('foo');
 	var e = window.event;
 	x.start_x=e.clientX;
	x.start_y=e.clientY;
}
function _onDrag(j,o){
#	dbg("CUSTOM drag x="+o.x+" y="+o.y+" over="+log_element(o.overElement));
	x.style.left=x.start_x-o.x;
	x.style.top=x.start_y-o.y;
} 
function _onDragEnd(j,o){
	dbg("CUSTOM dragend x="+o.x+" y="+o.y+" over="+log_element(o.overElement));
}

# *** Drag start + feedback ***
function _onDragStart2(){
	dbg("CUSTOM dragstart");
	x=new_anchor('div','<div style="width:16px;height:16px;background:blue;position:absolute;">AAA</div>');
 	var e = window.event;
 	x.start_x=e.clientX;
	x.start_y=e.clientY;
}
function _onDrag2(jc,o){
#	dbg("CUSTOM drag x="+o.x+" y="+o.y+" over="+log_element(o.overElement));
	x.firstChild.style.left=x.start_x-o.x;
	x.firstChild.style.top=x.start_y-o.y;
} 
function _onDragEnd2(jc,o){
	dbg("CUSTOM dragend x="+o.x+" y="+o.y+" over="+log_element(o.overElement));
	x.parentNode.removeChild(x);
}
||endif||


# Scope correction on inter object calls...
#   Work on this further if we actually decide to support our own interobject event handling
function Delegate() { }
Delegate.Null = function() { };
Delegate._create = function(a)
{
    var fn = function(){
        if (a.length == 2){
        	return a[1].apply(a[0], arguments);
        } else {
            for (var i=0; i<a.length; i+=2) a[i+1].apply(a[i], arguments);
            return null
        }
    };
    fn.invoke = fn;
    fn._targets = a;
    return fn;
};
Delegate.create = function(js, fn)
{
    if (!js) {
        fn.invoke = fn;
        return fn;
    }
    return Delegate._create([js,fn])
};

Delegate.combine = function(fn1, fn2)
{
    if (!fn1) {
        if (!fn2._targets) return Delegate.create(null, fn2);
        return fn2;
    }
    if (!fn2) {
        if (!fn1._targets) return Delegate.create(null, fn1);
        return fn1;
    }
    var c = fn1._targets ? fn1._targets : [null,fn1],
    	d = fn2._targets ? fn2._targets : [null,fn2];
    return Delegate._create(c.concat(d))
};
Delegate.remove = function(c, a)
{
    if (!c || c === a) return null;
    if (!a) return c;
    var b = c._targets, f = null, e;
    if (a._targets) {
        f = a._targets[0];
        e = a._targets[1]
    } else e = a;
    for (var d = 0; d < b.length; d += 2)
        if (b[d] === f && b[d + 1] === e) {
            if (b.length == 2) return null;
            b.splice(d, 2);
            return Delegate._create(b)
        }
    return c
};

||ifdef||_junk||
var Events = { };
Events.init = function(a) { a.__events = { } };
Events._attachEvent = function(a, b)
{
    this.__events[a] = Delegate.combine(this.__events[a], b);
    return this.__events[a]
};
Events._detachEvent = function(a, b)
{
    if (!this.__events[a]) return;

    this.__events[a] = Delegate.remove(this.__events[a], b);

    if (!this.__events[a]) delete this.__events[a]
};
Events._fireEvent = function(a, b)
{
    if (!this.__events[a]) return;

    this.__events[a].apply(this, b)
};
Events.addEventing = function(b)
{
    var a = b.prototype;

    if (a) {
        a.__events = null;
        a.attachEvent = Events._attachEvent;
        a.detachEvent = Events._detachEvent;
        a.fireEvent = Events._fireEvent
    }
    b.__events = { };
    b.attachEvent = Events._attachEvent;
    b.detachEvent = Events._detachEvent;
    b.fireEvent = Events._fireEvent
};

||endif||

||ifdef||_junk||
# Call "event" functions on resolved by string javascript objects

#eg
#	onclick="return Control.invoke('ReadingPane', '_onOpenBody', event);"
#	onmouseout="if(window.Control)Control.invoke(\'Microsoft.Live.ContactPicker.ContactPicker\', \'_onMouseFromContact\', event, this);"
#controlid, "method", event, otional param to fn - typ: DOM el (or event)
invoke = function(a, evt_id, e, p1) { 
	return Control._invokeImpl(a, evt_id, e, p1, true) 
};
function _invokeImpl(a, evt_id, e, p1, normal)
{
    if (window.__usingCompat) 
    	window.event = window.__getEventObj(e);				// Tweak some events for safari
    var ret = null;
    if (normal) {
    	// normal invoke
        if (!event) return;			
        var js = Control.findControl(event.srcElement, a);	// Find js object associated with DOM element
        if (!js || !js[evt_id]) return;
        ret = js[evt_id](p1);								// The function call
    } else {
        // invokeStatic call
        var js = Type.getType(a);							// Find js class objects 
        if (!js || !js[evt_id]) return;
        ret = js[evt_id](p1);								// The function call
    }

    if (window.__usingCompat) 
    	return event && event._returnValueSet ? event.returnValue : ret;
    return ret
};
||endif||

