// Class
function DragListener(sourceId, targetId, dragStartCallback, dropCallback, dragEndCallback){
    if(!sourceId){
        alert('Error in DragListener constructor! Parameter sourceId cannot be empty!');
        return false;
    }
    this.SourceId = sourceId;
    this.TargetId = targetId;
    var _DragStartCallback = dragStartCallback;
    var _DropCallback = dropCallback;
    var _DragEndCallback = dragEndCallback;
    
    this.RaiseDragStart = function(){
        if(_DragStartCallback!=null && typeof(_DragStartCallback) == 'function'){
            _DragStartCallback();
        }
    }
    this.RaiseDrop = function(){
        if(_DropCallback!=null && typeof(_DropCallback) == 'function'){
            _DropCallback();
        }
    }
    this.RaiseDragEnd = function(){
        if(_DragEndCallback!=null && typeof(_DragEndCallback) == 'function'){
            _DragEndCallback();
        }
    }
}

// Class
function DragAndDropManagerClass(){
    // "Constants"
    var ITEM_KEY_ATTRIBUTE_NAME = "DndIKey";
    var DELTA_X = 10;
    var DELTA_Y = 0;

    // Private fields
    var _IsMouseDown = false;
    var _IsDragging = false; // mouse was moved during mouse is down
    
    var _SourceAreaId = null;
    var _TargetAreaId = null;
    
    var _SourceItemKey = null;
    var _TargetItemKey = null;
    
    var _Listeners = new Array();
    
    var _IsNotIE = (document.all) ? false : true;
    
    var _MovingElement = null;
    var _mouseX = 0;
    var _mouseY = 0;
    var _startX = 0;
    var _startY = 0;
    
    var _OldMousemove = null;

    // Public Methods
    this.Init = function(){
        if(_IsNotIE){
            document.captureEvents(Event.MOUSEDOWN | Event.MOUSEMOVE | Event.MOUSEUP);
        }
        
        if(document.onmousedown){
            var oldMouseDown = document.onmousedown;
            document.onmousedown = function(e){ _Mousedown(e); oldMouseDown(e); };
        }else{
            document.onmousedown = _Mousedown;
        }
        
        if(document.onmouseup){
            var oldMouseUp = document.onmouseup;
            document.onmouseup = function(e){ _Mouseup(e); oldMouseUp(e); };
        }else{
            document.onmouseup = _Mouseup;
        }
    }    
    
    // Order of precedence:
    // - OnClientDragStart
    // - OnClientDrop    
    // - OnClientDragEnd
    this.AddListener = function(sourceId, targetId, dragStartCallback, dropCallback, dragEndCallback){
        _MarkDndSourceArea(sourceId);
        _MarkDndTargetArea(targetId);
        _Listeners[_Listeners.length] = new DragListener(sourceId, targetId, dragStartCallback, dropCallback, dragEndCallback);
    }
    
    this.AddItem = function(element, itemLocalKey){
        _MarkDndItemHost(element, itemLocalKey);
    }    
    
    this.SetMovingElement = function(el){
        _MovingElement = el;
        _startX = _mouseX;
        _startY = _mouseY;
        _mouseX
        if(el){
            _MovingElement.style.position = 'absolute';
            _MovingElement.style.posLeft = _mouseX;
            _MovingElement.style.posTop = _mouseY;                
            if (_IsNotIE){
                _startX = _MovingElement.offsetLeft;
                _startY = _MovingElement.offsetTop;
            }else{
                _startX = _MovingElement.offsetLeft;
                _startY = _MovingElement.offsetTop;
            }
        }
    }    
    
    this.get_SourceAreaId = function(){
        return _SourceAreaId;
    }
    this.get_TargetAreaId = function(){
        return _TargetAreaId;
    }
    this.get_SourceItemKey = function(){
        return _SourceItemKey;
    }
    this.get_TargetItemKey = function(){
        return _TargetItemKey;
    }
    
    // Event handlers
    var _Mousedown = function(ev){        
        _IsMouseDown = true;
        _FinishDrag(); // finish previous dragging
        _SourceAreaId = _GetDndSourceAreaId(ev);
        _SourceItemKey = _GetDndItemKey(ev);

        // Start listen mousemove
        if(_OldMousemove==null)
            _OldMousemove = document.onmousemove;
        document.onmousemove = _Mousemove;
    }    
    var _Mouseup = function(ev){
        var oldMouseDown = _IsMouseDown;
        _IsMouseDown = false;
        _TryRaiseDrop(ev, oldMouseDown, _IsDragging);
        _FinishDrag();
    }   
    var _Mousemove = function(ev){
        _UpdateIsMouseDown(ev);
        _UpdateMouseCoords(ev);
        
        if(_IsMouseDown && _MovingElement){
            _TryMoveElement();
        }
        // Try Raise DragStart
        var hasListeners = _HasListeners(_SourceAreaId);
        if( hasListeners
            && _IsMouseDown 
            && !_IsDragging 
        ){
            // If start dragging
            _IsDragging = true;
            _RaiseDragStart(_SourceAreaId);
        }
        
        // Try finish
        if(!_IsMouseDown && !_IsDragging){
            _FinishDrag();
        }        
        
        if(hasListeners && _IsDragging && _IsMouseDown){
            return _StopEvent(ev);
        }        
    }     


    var _UpdateIsMouseDown = function(ev){
        var isIE = (document.all) ? true : false ;
        if(isIE){
            _IsMouseDown = (event.button==1);
        }else{
            // There is no good way to obtain mouse button current state under Firefox
        }
    }

    var _UpdateMouseCoords = function(ev){
        var _oldMouseX =_mouseX;
        var _oldMouseY =_mouseY;
        if (_IsNotIE){
            _mouseX = ev.pageX;
            _mouseY = ev.pageY;
        }else{
            _mouseX = event.clientX;
            _mouseY = event.clientY;
        }
    }    
    var _FinishDrag = function(){
        if(_IsDragging){
            _RaiseDragEnd(_SourceAreaId);
        }
        _IsDragging = false;
        _MovingElement = null;
        
        document.onmousemove = _OldMousemove;
    }
    
    var _TryMoveElement = function(){
        if(_IsMouseDown && _MovingElement){
            _SetAbsolutePosition(_MovingElement, _mouseX+DELTA_X, _mouseY+DELTA_Y);
            _MovingElement.style.zIndex = 1000;
            // Cancel all text selections
            document.body.focus();
        }
    }
    
    var _TryRaiseDrop = function(ev, mouseDown, isDragging){
        if(mouseDown && isDragging){
            _TargetAreaId = _GetDndTargetAreaId(ev);
            _TargetItemKey = _GetDndItemKey(ev);
            _RaiseDrop(_SourceAreaId, _TargetAreaId);
        }
    }
    
    // Auxiliary methods ---
    
    var _FindListeners = function(sourceId, targetId){
        var results = new Array();
        for(var i=0; i<_Listeners.length; i++){
            var listener = _Listeners[i]; // type is DragListener
            if(listener.SourceId == sourceId && listener.TargetId == targetId){
                results[results.length] = listener;
            }
        }
        return results;
    }
    var _FindListenersBySource = function(sourceId){
        var results = new Array();
        for(var i=0; i<_Listeners.length; i++){
            var listener = _Listeners[i]; // type is DragListener
            if(listener.SourceId == sourceId){
                results[results.length] = listener;
            }
        }
        return results;
    }
    
    var _HasListeners = function(sourceAreaId){
        return (0 != _FindListenersBySource(sourceAreaId).length);
    }
    
    // Markers
    
    // Find drag-and-drop area which raise event
    var _GetDndSourceAreaId = function(ev){
        var srcElement = _IsNotIE ? ev.target : event.srcElement;
        var tmpEl = srcElement;
        while(tmpEl.parentNode && tmpEl.getAttribute("DndSource")!="true"){
            tmpEl = tmpEl.parentNode;
        }
        if(tmpEl && tmpEl.getAttribute){
            return tmpEl.id;
        }else{
            return '';
        }
    }
    
    var _MarkDndSourceArea = function(id){
        var el = document.getElementById(id);
        if(el){
            el.setAttribute("DndSource", "true");
        }else{
            alert("Cannot find Drag-And-Drop source area element");
        }
    }

    var _GetDndTargetAreaId = function(ev){
        var srcElement = _IsNotIE ? ev.target : event.srcElement;
        var tmpEl = srcElement;
        while(tmpEl.parentNode && tmpEl.getAttribute("DndTarget")!="true"){
            tmpEl = tmpEl.parentNode;
        }
        if(tmpEl && tmpEl.getAttribute){
            return tmpEl.id;
        }else{
            return '';
        }
    }
    
    var _MarkDndTargetArea = function(id){
        if(id==null)
            return;
        var el = document.getElementById(id);
        if(el){
            el.setAttribute("DndTarget", "true");
        }else{
            alert("Cannot find Drag-And-Drop target area element");
        }
    }
    
    // Find drag-and-drop item within drag-and-drop area which raised event
    var _GetDndItemKey = function(ev){
        var srcElement = _IsNotIE ? ev.target : event.srcElement;
        var tmpEl = srcElement;
        
        while(tmpEl.parentNode && !tmpEl.getAttribute(ITEM_KEY_ATTRIBUTE_NAME)){
            tmpEl = tmpEl.parentNode;
        }
        var ret = '';
        if(tmpEl && tmpEl.getAttribute){
            ret = tmpEl.getAttribute(ITEM_KEY_ATTRIBUTE_NAME);
        }
        return ret;
    }
    var _MarkDndItemHost = function(el, key){
        if(el){
            el.setAttribute(ITEM_KEY_ATTRIBUTE_NAME, key);
        }
    }

    // Raise events
    var _RaiseDragStart = function(sourceId){
        var listeners = _FindListenersBySource(sourceId);
        for(var i=0; i<listeners.length; i++){
            listeners[i].RaiseDragStart();
        }
    }
    var _RaiseDragEnd = function(sourceId){
        var listeners = _FindListenersBySource(sourceId);
        for(var i=0; i<listeners.length; i++){
            listeners[i].RaiseDragEnd();
        }
    }
    var _RaiseDrop = function(sourceId, targetId){
        var listeners = _FindListeners(sourceId, targetId);
        for(var i=0; i<listeners.length; i++){
            listeners[i].RaiseDrop();
        }
    }
    
    // HTML DOM Utils ---    
    function _StopEvent(evt){
        if(window.event){
            if(!evt)
                evt = window.event;
            evt.cancelBubble = true;
            evt.returnValue = false;
        }else{
            if(evt.preventDefault)
                evt.preventDefault();
            if(evt.stopPropagation)
                evt.stopPropagation();
        }
        return false;
    }
    
    var _SetAbsolutePosition = function(el, dx, dy){
        if(el){
            var isNotIE = (document.all) ? false : true;
            if(el.style.position != 'absolute'){
                el.style.position = 'absolute';
            }
            if (isNotIE){
                el.style.left = dx + document.body.scrollLeft;
                el.style.top = dy + document.body.scrollTop;
            }else{
                el.style.pixelLeft = dx + document.body.scrollLeft;
                el.style.pixelTop = dy + document.body.scrollTop;
            }
        }
    }

}

// Create instance
var DragAndDropManager = new DragAndDropManagerClass();
//DragAndDropManager.Init();
