/// Attention!  
/// Include of utils.js is required (it can looks like WrbResource.axd?...)!

// DragAndDropManagerClass class ==================================================

function DragAndDropManagerClass(){
    // "Constants"
    var ITEM_KEY_ATTRIBUTE_NAME = "DndIKey";
    var ELEMENT_NODE = 1;
    
    /// If we use DELTA_X!=10 or DELTA_Y != 0 then mouse cursor can lay under dragging element.
    /// It would means that neither event.srcElement not event.target can be used tom obtain event target!
    /// It would means change of event handling algorithmes!
    var DELTA_X = 10;
    var DELTA_Y = 0;

    // Private fields ---
    var _this = null;
    var _IsInitialized = false;
    var _IsMouseDown = false;
    var _IsDragging = false; // mouse was moved during mouse is down
    
    var _SourceAreaId = null;
    var _TargetAreaId = null;
    
    var _SourceItemKey = null;
    var _SourceItemElement = null;
    var _TargetItemKey = null;
    
    var _Listeners = new Array();
    
    var _IsNotIE = (document.all) ? false : true;
    
    var _MovingElement = null;
    var _MovingElementIsClone = false;
    var _OldBodyCursor = document.body ? document.body.style.cursor : 'auto';
    
    var _OnlyItemsDraggableCollection = new Object();
    
    var _mouseX = 0;
    var _mouseY = 0;
    var _startX = 0;
    var _startY = 0;
    
    var _OldMousemove = null;
    
    var _OverflowHelper = new OverflowHelper();

    // Main Public Methods ---
    this.Init = function(){
        if(_IsInitialized)
            return;
        
        _this = this;
            
        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;
        }

        _OldBodyCursor = document.body ? document.body.style.cursor : 'auto';
        
        _IsInitialized = true;
    }    
    
    // 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.AddDropListener = function(sourceId, targetId, dropCallback){
        this.AddListener(sourceId, targetId, null, dropCallback, null);
    }
    
    this.AddItem = function(element, itemLocalKey){
        _MarkDndItemHost(element, itemLocalKey);
    }    
    
    this.get_SourceAreaId = function(){
        return _SourceAreaId;
    }
    this.get_TargetAreaId = function(){
        return _TargetAreaId;
    }
    this.get_SourceItemKey = function(){
        return _SourceItemKey;
    }
    this.get_TargetItemKey = function(){
        return _TargetItemKey;
    }

    // Decoration members ---
    
    this.SetMoveDecoration = function(sourceId, movingDecorationTemplateId, bodyCursor){
        var movingDecorationTemplate = document.getElementById(movingDecorationTemplateId);
        var _this = this;
        this.AddListener(
            sourceId, 
            null, // Target ID doesn't required for decoration purposes
            // DragStart
            function(){ 
                _this._SetMovingDecoration(movingDecorationTemplate, bodyCursor);
            }, 
            // Drop
            null,
            // DragEnd
            function(){ 
                _this._ReleaseMovingDecoration();
            }
        );
    }

    this.UseItemCloneAsDecoration = function(sourceId, bodyCursor){
        var _this = this;
        this.AddListener(
            sourceId, 
            null, // Target ID doesn't required for decoration purposes
            // DragStart
            function(){ 
                var _movingDecorationTemplate = _SourceItemElement;
                if(!_IsOnlyItemsDraggable(sourceId)){
                    var _movingDecorationTemplate = _SourceItemElement;
                    if(_movingDecorationTemplate == null){
                        _movingDecorationTemplate = document.getElementById(_this.get_SourceAreaId());
                    }
                }
                if(_movingDecorationTemplate){
                    _this._SetMovingDecoration(
                        _movingDecorationTemplate, 
                        bodyCursor
                        );
                }
            }, 
            // Drop
            null,
            // DragEnd
            function(){ 
                _this._ReleaseMovingDecoration();
            }
        );
    }
    
    this.SetMovingElement = function(el, isClone){
        if(isClone==null)
            isClone = false;
            
        _MovingElementIsClone = isClone;
        _MovingElement = el;
        _startX = _mouseX;
        _startY = _mouseY;
        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._SetMovingDecoration = function(el, bodyCursor){
        if(el!=null){
            var movingElementOrig = el;
            movingElement = movingElementOrig.cloneNode(true);
            movingElement.id = '';
            document.body.appendChild(movingElement);
            this.SetMovingElement(movingElement, true); 
        }
        _OldBodyCursor = document.body.style.cursor;
        if(bodyCursor!=null){
            document.body.style.cursor = bodyCursor;
        }
    }
    
    this._ReleaseMovingDecoration = function(){
        if(_MovingElementIsClone && _MovingElement){
            _MovingElement.parentNode.removeChild(_MovingElement);
        }
        this.SetMovingElement(null); 
        if(document.body)
            document.body.style.cursor = _OldBodyCursor;
    }

    /// Only items can be dragged. 
    /// If user try drag SourceArea outside any item dragging process will not be started!!!
    this.SetOnlyItemsIsDraggable = function(sourceId){
        _OnlyItemsDraggableCollection["__"+sourceId] = true;
    }
    var _IsOnlyItemsDraggable = function(sourceId){
        return (_OnlyItemsDraggableCollection["__"+sourceId] == true);
    }    
    var _DragEnabled = function(sourceAreaId, sourceItemKey){
        if(sourceItemKey){
            return true;
        }else{
            return !_IsOnlyItemsDraggable(sourceAreaId)
        }
    }
    
// Event handlers
    
    // MOUSEDOWN
    var _Mousedown = function(ev){        
        _IsMouseDown = true;
        _FinishDrag(); // finish previous dragging
    
        var sourceAreaId = _GetDndSourceAreaId(ev);
        var sourceItemElement = _GetDndItemElement(ev);
        var sourceItemKey = _GetDndItemKeyByElement(sourceItemElement);
        
        if(!_DragEnabled(sourceAreaId, sourceItemKey)){
            _IsMouseDown = false;
            return;
        }

        _SourceAreaId = sourceAreaId;
        _SourceItemElement = sourceItemElement;
        _SourceItemKey = sourceItemKey;
        
        //_OverflowHelper.Reset();

        // Start listen mousemove
        if(_OldMousemove==null)
            _OldMousemove = document.onmousemove;
        document.onmousemove = _Mousemove;
        
        if(sourceAreaId){
            return _StopEvent(ev);
        }        
    }    
    
    // MOUSEUP
    var _Mouseup = function(ev){
        var oldMouseDown = _IsMouseDown;
        _IsMouseDown = false;
        _TryRaiseDrop(ev, oldMouseDown, _IsDragging);
        _FinishDrag();
    }   
    
    // MOUSEMOVE
    var _Mousemove = function(ev){
    
        //_OverflowHelper.DisableOverflow(_SourceAreaId);
        
        _UpdateIsMouseDown(ev);
        _UpdateMouseCoords(ev);
        
        if(_IsMouseDown && _IsDragging && _MovingElement){
            // Continue dragging
            _TryMoveElement();
        }
        
        // Start dragging
        // Try Raise DragStart
        var hasListeners = _HasListeners(_SourceAreaId);
        if( hasListeners
            && _IsMouseDown 
            && !_IsDragging 
            )
        {
            // If start dragging
            _IsDragging = true;
            _RaiseDragStart(_SourceAreaId);
            _OverlayIFramesON();
        }
        
        //_OverflowHelper.RestoreOverflow(_SourceAreaId);
    
        // Try finish
        if(!_IsMouseDown && !_IsDragging){
            _FinishDrag();
        }        
        
        if(hasListeners && _IsDragging && _IsMouseDown){
            return _StopEvent(ev);
        }        
    }


    // Event handling helpers ---
    
    var _UpdateIsMouseDown = function(ev){
        if(_IsNotIE){
            // There is no good way to obtain mouse button current state under Firefox
        }else{
            _IsMouseDown = (event.button==1);
        }
    }

    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){
            _OverlayIFramesOFF();
            _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;
            /*commented because http://bugs.plesk.ru/show_bug.cgi?id=132413
            // Cancel all text selections
            document.body.focus();
            */
        }
    }
    
    var _TryRaiseDrop = function(ev, mouseDown, isDragging){
        if(mouseDown && isDragging){
            _TargetAreaId = _GetDndTargetAreaId(ev);
            _TargetItemKey = _GetDndItemKeyByEvent(ev);
            _RaiseDrop(_SourceAreaId, _TargetAreaId);
        }
    }
    
    // Debugging ---
    
    this.OnWriteDebug = null; //Delegate
    var _MessCounter = 0;
    var _WriteDebug = function(mess){
        if(_this.OnWriteDebug && typeof(_this.OnWriteDebug)=='function'){
           _this.OnWriteDebug("MessCounter="+ _MessCounter +"<br/>\n"+ mess);
           _MessCounter++;
        }
    }

    // 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){
        // TMP DEBUG CODE (begin)
//        return _TargetAreaId;
        // TMP DEBUG CODE (end)
    
        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");
            // TMP DEBUG CODE (begin)
//            el.onmouseover = function(ev){ 
//                _TargetAreaId = id; 
//                _WriteDebug( "_TargetAreaId = " + _TargetAreaId);
//                }
//            el.onmouseout = function(ev){ 
//                if(_TargetAreaId == id) 
//                    _TargetAreaId = null;
//                _WriteDebug( "_TargetAreaId = " + _TargetAreaId);
//                }  
            // TMP DEBUG CODE (end)
        }else{
            alert("Cannot find Drag-And-Drop target area element");
        }

//        // Code below is working!
//        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 _GetDndItemKeyByEvent = function(ev){
        var tmpEl = _GetDndItemElement(ev);
        var ret = '';
        if(tmpEl && tmpEl.getAttribute){
            ret = tmpEl.getAttribute(ITEM_KEY_ATTRIBUTE_NAME);
        }
        return ret;
    }
    var _GetDndItemKeyByElement = function(el){
        if(el && el.getAttribute){
            return el.getAttribute(ITEM_KEY_ATTRIBUTE_NAME);
        }else{
            return '';
        }
    }
    var _GetDndItemElement = function(ev){
        var srcElement = _IsNotIE ? ev.target : event.srcElement;
        var tmpEl = srcElement;
        
        while(tmpEl.parentNode && tmpEl.getAttribute && !tmpEl.getAttribute(ITEM_KEY_ATTRIBUTE_NAME)){
            tmpEl = tmpEl.parentNode;
        }
        if(tmpEl && tmpEl.getAttribute && tmpEl.getAttribute(ITEM_KEY_ATTRIBUTE_NAME)){
            return tmpEl;
        }else{
            return null;
        }
    }
    var _MarkDndItemHost = function(el, key){
        if(el){
            el.setAttribute(ITEM_KEY_ATTRIBUTE_NAME, key);
        
            // TMP DEBUG CODE (begin)
//            el.onmouseover = function(ev){ 
//                _TargetItemKey = key; 
//                _WriteDebug( "_TargetItemKey = " + _TargetItemKey);
//                };
//            el.onmouseout = function(ev){ 
//                if(_TargetItemKey == key) 
//                    _TargetItemKey = null; 
//                _WriteDebug( "_TargetItemKey = " + _TargetItemKey);
//                };
            // TMP DEBUG CODE (end)            
        }
    }

    // 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 ---   
    var _GetEventSrc = function(ev){
        return _IsNotIE ? ev.target : event.srcElement;
    }
     
    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){
            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;
            }
        }
    }

    var _OverlayIFramesON = function(){
        var iframes = document.getElementsByTagName('iframe');
        if(iframes){
            for(var i=0; i<iframes.length; i++){
                _SetOverlay(iframes[i]);
            }
        }
    }
    var _OverlayIFramesOFF = function(){
        var iframes = document.getElementsByTagName('iframe');
        if(iframes){
            for(var i=0; i<iframes.length; i++){
                _RemoveOverlay(iframes[i]);
            }
        }
    }
    
    var _SetOverlay = function(el){
        // Create element
	    var overlay = document.createElement("IMG");

        // Add to hierarchy	    
	    el.parentNode.insertBefore(overlay, el);
	    
	    // Store data for "_RemoveOverlay()"
	    el._Overlay = overlay;
	    
        // Set styles	    
        overlay.style.zIndex = 1 + (el.style.zIndex || 0);
	    overlay.style.position ="absolute";
	    overlay.style.display = "block";
	    sw_SetOpacity(overlay, 0);// function from utils.js

        // Code for debugging:
        //{
        //    overlay.style.border = '2px blue solid'; 
        //    sw_SetOpacity(overlay, 100);
        //}                        
	    
	    // Set position and size
	    var rect = new sw_Position(el);
	    overlay.style.width = rect.width;
	    overlay.style.height = rect.height;
	    /// Old code. It cause problems if iframe lays within scrolled DIV!
	    //overlay.style.top = rect.top;
	    //overlay.style.left = rect.left;
	    if(!_IsNotIE){
	        _FixPositionForIE(overlay, rect.left, rect.top);
        }
    } 
    
    var _RemoveOverlay = function(el){
        if(!el._Overlay)
            return;
        el._Overlay.parentNode.removeChild(el._Overlay);
    }

    var _FixPositionForIE = function(el, x, y){
        // Temporary fix
	    var rect = new sw_Position(el);
	    if(rect.left != x){
	        var dx = (x - rect.left);
	        el.style.left = (x + dx);
	    }
	    if(rect.top != y){
	        var dy = (y - rect.top);
	        el.style.top = (y + dy);
	    }
    }
    
}//DragAndDropManagerClass - end

// Create instance
var DragAndDropManager = new DragAndDropManagerClass();
//DragAndDropManager.Init(); // We should run initialization manually!


// DragListener 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();
        }
    }
}

// OverflowHelper class ============================================================

/// Class allow us temporary Enable/Disable overflow:auto style settings to solve problem during drag-and-drop.
/// Class below can be optimized by using caching!
function OverflowHelper(){
    var ELEMENT_NODE = 1;
    
    var _IsOverflowFixed = false;
    var _IsOverflowRestored = false;
    var _IsInProcessing = false;
    
    var _IsNotIE = (document.all) ? false : true;
    
    
    // Public methods (begin)
    this.Reset = function(){
        if(!_IsNotIE)
            return;            
        _IsOverflowFixed = false;
        _IsOverflowRestored = false;
        _IsInProcessing = false;
    }    
    
    this.DisableOverflow = function(sourceAreaId){
        if(_IsNotIE && !_IsOverflowFixed){
            if(sourceAreaId==null){
                alert('Error: Empty parameter sourceAreaId!');
                return;
            }
            //_IsOverflowFixed = true;
            _DisableOverflowByEl(document.getElementById(sourceAreaId));            
            _IsOverflowFixed = true;
        }
    }        
    this.RestoreOverflow = function(sourceAreaId){
        if(_IsNotIE && !_IsOverflowRestored){        
            if(sourceAreaId==null){
                alert('Error: Empty parameter sourceAreaId!');
                return;
            }
            //_IsOverflowRestored = true;
            _RestoreOverflowByEl(document.getElementById(sourceAreaId));
            _IsOverflowRestored = true;
        }
    }
    // Public methods (end)

    function _DisableOverflowByEl(el){
        if(!el || el.nodeType != ELEMENT_NODE){
            return;
        }

        if(el.style.overflow || el.style.overflowX || el.style.overflowY){
            if(!el.OriginalOverflow)
                el.OriginalOverflow = new Object();
            // Store original values    
            el.OriginalOverflow.overflow = el.style.overflow;
            el.OriginalOverflow.overflowX = el.style.overflowX;
            el.OriginalOverflow.overflowY = el.style.overflowY;
        }
        
        // Set new values    
        var newVal = 'visible';
        el.style.overflow = newVal;
        el.style.overflowX = newVal;
        el.style.overflowY = newVal;
        
        _DisableOverflowByEl(el.parentNode);
    }        
    function _RestoreOverflowByEl(el){
        if(!el || el.nodeType != ELEMENT_NODE){
            return;
        }

        // Restore original values    
        if(el.OriginalOverflow){
            el.style.overflow =  el.OriginalOverflow.overflow;
            el.style.overflowX = el.OriginalOverflow.overflowX;
            el.style.overflowY = el.OriginalOverflow.overflowY;
        }else{
            el.style.overflow =  '';
            el.style.overflowX = '';
            el.style.overflowY = '';
        }
        
        _RestoreOverflowByEl(el.parentNode);
    }

}

// sw_Position ==================================================================================
function sw_Position(element)
{    
    if(!element){
        alert('Incorrect argument in sw_Position ctor!');
        debugger;
    }
	var left = 0;
	var top  = 0;
	
	var width = element.offsetWidth;
	var height = element.offsetHeight;
    
    // Init (begin)
    while (element.offsetParent)
    {
	    left += element.offsetLeft;
	    top += element.offsetTop;
		
	    element = element.offsetParent;
        if (element) {
            p = element.style.position;
            if (p == 'relative' || p == 'absolute') break;
        }
    }
    	
	if(element.x)
	    left = element.x;
		
	if(element.y)
	    top = element.y;
    // Init (end)

    // Public fields
	this.left   = (null != left ? left : 0);
	this.top    = (null != top ? top : 0);
	this.width  = (null != width ? width : 0);
	this.height = (null != height ? height : 0);
	
	this.right  = left + width;
	this.bottom = top + height;
}






