var DRAGDROP_DRAGOVER_CLASS         = "DragOverClass";
var DRAGDROP_DRAGINGORIGINAL_CLASS  = "DragingOverClass";
var DRAGDROP_DRAGINGCOPY_CLASS      = "DragingCopyClass";
var DRAGDROP_DROPOVER_CLASS         = "DropOverClass";
var DRAGDROP_DROPSELECT_CLASS       = "DropSelectClass";
var DRAGDROP_POPUP_SUFFIX           = '_popup';

var DRAGDROP_MOVE           = 0x00000000;
var DRAGDROP_COPY           = 0x00000001;
var DRAGDROP_POPUP          = 0x00000002;
var DRAGDROP_EVENT_DROP     = "Drop";
var DRAGDROP_FUZZY_AREA     = 5;

/** This variable is set to true when the restoring process is running */
var DragDrop_gbRestoringRun = false;

function DragDrop_Object()
{
    /** DOM element reference */
    this.oElement = null;

    /** Original mouse pointer position */
    this.iOriginalMouseX = 0;
    this.iOriginalMouseY = 0;

    /** Object original position */
    this.oOriginalDimensions = null;

    /** Object current position */
    this.oCurrentDimensions = null;
    
    /** Flags */
    this.xFlags = DRAGDROP_MOVE;
}

function DragDrop_DragContext()
{
    /** Dragged element */
    this.oElement = null;
    
    /** Drag flags */
    this.xDragFlags = 0;
}

/**
 * Hash of droppable elements
 */
var DragDrop_gasDropableElementList = new Array();

/**
 * Hash of selected elements
 */
var DragDrop_goDragElementList = null;

/**
 * Current target for a drag & drop event
 */
var DragDrop_goTarget = null;

/*****************************
 * CONTEXT STRUCTURE
 */
function DragDrop_Context()
{
    this.aoSourceElementList = new Array();
    this.oTargetElement      = null;
}

/*****************************/

/**
 * Adds the Over class
 */
function DragDrop_DragOver(_oElement)
{
    // Valid elemnt (and not already draging) ?
    if((_oElement != null)
    && (!Tools_HasClass(_oElement, DRAGDROP_DRAGINGCOPY_CLASS))
    && (!Tools_HasClass(_oElement, DRAGDROP_DRAGINGORIGINAL_CLASS))
    && (!Tools_HasClass(_oElement, DRAGDROP_DRAGOVER_CLASS))
    && (!Tools_HasClass(_oElement, DRAGDROP_DROPOVER_CLASS))
    && (DragDrop_goDragElementList == null))
    {
        // Sets style
        Tools_AddClass(_oElement, DRAGDROP_DRAGOVER_CLASS);
    }
}

/**
 * Remove the over class
 */
function DragDrop_DragOut(_oElement)
{
    // Valid element ?
    if(_oElement != null)
    {
        // Sets style
        Tools_RemoveClass(_oElement, DRAGDROP_DRAGOVER_CLASS);
    }
}

/**
 * Restore the original position of all the currently dragged elements
 */
function DragDrop_RestoreOriginalPosition()
{
    // Not already restoring ?
    if(DragDrop_gbRestoringRun)
    {
        
    }
}

function DragDrop_AddTarget(_sControlID)
{
    // Adds a control in the dropable table (not already added)
    if((_sControlID != null)
    && (Tools_IndexOf(DragDrop_gasDropableElementList, _sControlID) < 0))
    {
        // Adds it
        DragDrop_gasDropableElementList.push(_sControlID);
    }
}

function DragDrop_RemoveTarget(_sControlID)
{
    // Adds a control in the dropable table (not already added)
    if(_sControlID != null)
    {
        // Valid ?
        var oTempArray = new Array();
        // Found, removes it
        var iNewIdx = 0;
        for(var i=0; i<DragDrop_gasDropableElementList.length; i++)
        {
            // Gets control
            var sTarget = DragDrop_gasDropableElementList[i];
            
            // Not node to remove ?
            if(sTarget != _sControlID)
            {
                oTempArray[iNewIdx] = DragDrop_gasDropableElementList[i];
                iNewIdx++;
            }
        }
        
        // Assigns new array
        DragDrop_gasDropableElementList = oTempArray;
    }
}

function DragDrop_RemoveAllTarget(_sVFormID)
{
    // Adds a control in the dropable table (not already added)
    if(_sVFormID != null)
    {
        // Valid ?
        var oTempArray = new Array();
        // Found, removes it
        var iNewIdx = 0;
        for(var i=0; i<DragDrop_gasDropableElementList.length; i++)
        {
            // Gets control
            var sTarget = DragDrop_gasDropableElementList[i];
            
            // Control starts with VForm ID ?
            if(sTarget.indexOf(_sVFormID + TOOLS_CONTROL_ID_SEPARATOR) < 0)
            {
                oTempArray[iNewIdx] = DragDrop_gasDropableElementList[i];
                iNewIdx++;
            }
        }
        
        // Assigns new array
        DragDrop_gasDropableElementList = oTempArray;
    }
}

function _private_DragDrop_SelectNewTarget(_oTarget)
{
    // Remove the selection on the previous targeted element
    if(DragDrop_goTarget != null)
    {
        Tools_RemoveClass(DragDrop_goTarget, DRAGDROP_DROPOVER_CLASS);
    }
    
    // Valid target ?
    if(_oTarget != null)
    {
        Tools_AddClass(_oTarget, DRAGDROP_DROPOVER_CLASS);
    }
}

function _private_DragDrop_MoveRequested(_oDragContext)
{
    // Default return value
    var bRet = false;
    
    // Valid context ?
    if(_oDragContext != null)
    {
        if((Math.abs(_oDragContext.iOriginalMouseX - Tools_giMouseX) > DRAGDROP_FUZZY_AREA)
        || (Math.abs(_oDragContext.iOriginalMouseY - Tools_giMouseY) > DRAGDROP_FUZZY_AREA))
        {
            bRet = true;
        }
    }
    
    // Returns move request status
    return bRet;
}

function _private_DragDrop_MouseMove(_oDragContext)
{
    // Valid drag context ?
    if(_oDragContext != null)
    {
        // Need to move object ?
        if(_private_DragDrop_MoveRequested(_oDragContext))
        {
            // Cancel the mouse move detection
            Event_RemoveAction(EVENT_MOUSE_MOVE, _private_DragDrop_MouseMove);
            
            DragDrop_goDragElementList = new Object();
            DragDrop_goDragElementList[_oDragContext.oElement.id] = _oDragContext;
            
            // Do not adds class for Popup draged elements
            if((_oDragContext.xFlags & DRAGDROP_POPUP) != DRAGDROP_POPUP)
            {
                // TODO : After a quick review, I think that there should have a copy
                // of the full list of dragged element. Check if it has to be the case or not.
                
                // Update the CSS of the dragged element (check if needed on list ?)
                Tools_RemoveClass(_oDragContext.oElement, DRAGDROP_DRAGOVER_CLASS);
                Tools_RemoveClass(_oDragContext.oElement, DRAGDROP_DROPOVER_CLASS);
                
                // Copy object ?
                var oOriginalNode = _oDragContext.oElement;
                if(((_oDragContext.xFlags & DRAGDROP_COPY) == DRAGDROP_COPY)
                && (!Tools_IsCopy(_oDragContext.oElement)))
                {
                    // Clone the DOM Element
                    var oCopyNode           = Tools_Copy(oOriginalNode);
    
                    // Force dimensions
                    var oPos = Tools_GetDimensions(oOriginalNode);
                    
                    _oDragContext.oElement  = oCopyNode;
                    
    //                var oRoot = document.getElementById('BodyContent');
    //                oRoot.insertBefore(oCopyNode, oRoot.childNodes[0]);
                    oOriginalNode.parentNode.insertBefore(oCopyNode, oOriginalNode);
                    
                    // Adds the dragging class
                    Tools_AddClass(_oDragContext.oElement, DRAGDROP_DRAGINGCOPY_CLASS);
                }
                
                // Adds the dragging class
                Tools_AddClass(oOriginalNode, DRAGDROP_DRAGINGORIGINAL_CLASS);
                
                // Adds a class on all the target elements
/*
                for(var i=0; i<DragDrop_gasDropableElementList.length; i++)
                {
                    // Gets control
                    var oDropTarget = document.getElementById(DragDrop_gasDropableElementList[i]);
                    
                    // Valid ?
                    if(oDropTarget != null)
                    {
                        Tools_AddClass(oDropTarget, DRAGDROP_DROPSELECT_CLASS);
                    }
                }
*/            
            }
            // TODO : Reacts to the CTRL button state (multiple selection)
            
            // Simulate a first move step
            DragDrop_Move(_oDragContext.oElement);
        }
    }
}

function DragDrop_Move()
{
    // The mouse button is not clicked ?
    // It means that there is a mouse up that has not been catched by the browser => Force it
    if((Tools_gxMouseB & EVENT_CLICK_LEFT) != EVENT_CLICK_LEFT)
    {
        DragDrop_Drop();
    }
    
    // Valid draged element ?
    if(DragDrop_goDragElementList != null)
    {
        // 1 - UPDATE THE POSITION OF THE DRAGGED ELEMENTS
        
        // Get the current object to move
        
        var bPopup = false;
        
        // Loop on all the element in the list
        for(var sID in DragDrop_goDragElementList)
        {
            // Gets Object
            var oObject = DragDrop_goDragElementList[sID];
            
            // Save new position
            oObject.oCurrentDimensions.iLeft = oObject.oOriginalDimensions.iLeft + (Tools_giMouseX - oObject.iOriginalMouseX);
            oObject.oCurrentDimensions.iTop  = oObject.oOriginalDimensions.iTop  + (Tools_giMouseY - oObject.iOriginalMouseY);
            
            // Set the new position in the CSS (renders it)
            oObject.oElement.style.position = "absolute";
            oObject.oElement.style.left     = oObject.oCurrentDimensions.iLeft.toString(10) + 'px';
            oObject.oElement.style.top      = oObject.oCurrentDimensions.iTop.toString(10)  + 'px';
            bPopup = bPopup || ((oObject.xFlags & DRAGDROP_POPUP) == DRAGDROP_POPUP);
        }
        
        // 2 - UPDATE THE BEHAVIOUR OF THE DROP TARGET

        // Not popup ?
        if(!bPopup)
        {
            // Get the drop target selected
            var oDropTarget = DragDrop_GetCurrentTarget();
            
            // Found a different that the previous targeted ?
            if(oDropTarget != DragDrop_goTarget)
            {
                // The target has changed.
                _private_DragDrop_SelectNewTarget(oDropTarget);
            }
    /* *********** THIS SECTION IS COMMENTED OUT. IT INCREASES THE PROCESSING TIME
     * *********** OF THE RENDERING A LOT. WAIT FOR A BROWSER FIX
            // Valid target ?
            if(oDropTarget != null)
            {
                document.documentElement.style.cursor = "default";
            }
            else
            {
                document.documentElement.style.cursor = "not-allowed";
            }
*/
        
            // Stores the target if valid (not the same as the draged one
            DragDrop_goTarget = oDropTarget;
        }
    }
}

function DragDrop_GetCurrentTarget()
{
    // Default return value
    var oRet = null;
/*
    var sDebug = "MOUSEX : " + Tools_giMouseX + ", MOUSEY : " + Tools_giMouseY + "<br>";
    sDebug += ", MOUSE BUTTONS : ";
    if((Tools_gxMouseB & EVENT_CLICK_LEFT) == EVENT_CLICK_LEFT)
    {
        sDebug += "- LEFT -";
    }
    if((Tools_gxMouseB & EVENT_CLICK_MIDDLE) == EVENT_CLICK_MIDDLE)
    {
        sDebug += "- MIDDLE -";
    }
    if((Tools_gxMouseB & EVENT_CLICK_RIGHT) == EVENT_CLICK_RIGHT)
    {
        sDebug += "- RIGHT -";
    }
*/
    // Traverse all the droppable elements and get their coordinates
    for(var i=0; i<DragDrop_gasDropableElementList.length; i++)
    {
        // Gets possible Target
        var oTarget = document.getElementById(DragDrop_gasDropableElementList[i]);
        
        // Valid ?
        if((oTarget != null)
        && (!Tools_ContainsKey(DragDrop_goDragElementList, oTarget.id)))
        {
            // Get positions
            var oPos = Tools_GetDimensions(oTarget);

            // Over ?
            if(Tools_IsMouseInRegion(oPos))
            {
                // Selects it
                oRet = oTarget;
            }
/*
            // Write debug
            sDebug += "<br>NAME = " + oTarget.id;
            sDebug += ",X : " + oPos.iLeft;
            sDebug += ",Y : " + oPos.iTop;
            sDebug += ",W : " + oPos.iWidth;
            sDebug += ",H : " + oPos.iHeight;
*/
        }
    }
/*
    // WRITE DEBUG
    var oDebugElement = document.getElementById('DVF@ParserErrors_value');
    if(oDebugElement != null)
    {
        oDebugElement.innerHTML = sDebug;
    }
*/
    // Return the selected target
    return oRet;
}

function _private_DragDrop_Drag(_oContext)
{
    // Remove the event from the list
    Event_RemoveAction(EVENT_MOUSE_DOWN, _private_DragDrop_Drag);
    
    // Gets element and option flags
    var oElement     = _oContext.oElement;
    
    // Valid and left button ?
    if((oElement != null)
    && ((Tools_gxMouseB & EVENT_CLICK_LEFT) == EVENT_CLICK_LEFT))
    {
        // Element clicked down
        
        // Register new callback for the next events:
        // 1 - call the update function for the mouse move
        // 2 - call the drop function for the mouse up
        Event_AddAction(EVENT_MOUSE_MOVE, DragDrop_Move);
        Event_AddAction(EVENT_MOUSE_UP, DragDrop_Drop);

        // 3 - the mouse down event has to be removed from the Browser default
        // behaviour (remove the mouse selection)

        // Initialize variables
        var oObject                 = new DragDrop_Object();
        oObject.oElement            = oElement;
        oObject.oOriginalDimensions = Tools_GetDimensions(oElement);
        oObject.oCurrentDimensions  = Tools_GetDimensions(oElement);
        oObject.iOriginalMouseX     = Tools_giMouseX;
        oObject.iOriginalMouseY     = Tools_giMouseY;
        oObject.xFlags              = _oContext.xDragFlags;
        
        // Detect movement
        Event_AddAction(EVENT_MOUSE_MOVE, _private_DragDrop_MouseMove, oObject);
    }
}

/**
 * Drag a component
 */
function DragDrop_Drag(_oElement, _xOptionFlags)
{
    // Popup ?
    if((_xOptionFlags & DRAGDROP_POPUP) == DRAGDROP_POPUP)
    {
        // Gets ID
        var sID = _oElement.id
        
        // Gets Popup VForm ID
        var iSplitterIdx = sID.indexOf(TOOLS_CONTROL_ID_SEPARATOR);
        
        // Found ?
        if(iSplitterIdx > 0)
        {
            // Builds ID
            var sPopupID = sID.substr(0, iSplitterIdx) + DRAGDROP_POPUP_SUFFIX;
            
            // Gets element
            var oPopup = document.getElementById(sPopupID);
            
            // Valid ?
            if(oPopup != null)
            {
                // Uses it
                _oElement = oPopup;
            }
        }
    }
    
    // Register a new Event (post spawn event)
    var oContext = new DragDrop_DragContext();
    oContext.oElement   = _oElement;
    oContext.xDragFlags = _xOptionFlags;
    Event_AddAction(EVENT_MOUSE_DOWN, _private_DragDrop_Drag, oContext);
    gbBubble = false;
}

function _private_DragDrop_ValidDrop(_oDropContext)
{
    // Left click up ?
    if((Tools_gxMouseB & EVENT_CLICK_LEFT) != EVENT_CLICK_LEFT)
    {
        // Release target style
        if(DragDrop_goTarget != null)
        {
//            DragDrop_SetOutStyle(DragDrop_goTarget);
        }
        
        DragDrop_goTarget = null;
        
        // Remove the copy name for all the Copy elements
        var asDragList = _oDropContext.aoSourceElementList;
        
        // Remove for all frag list
        for(var i=0; i<asDragList.length; i++)
        {
            asDragList[i] = Tools_RemoveCopySuffix(asDragList[i]);
        }
        
        _oDropContext.aoSourceElementList = asDragList;
        
        // Adds Global Drop event
        Event_Execute(DRAGDROP_EVENT_DROP, null, _oDropContext);
    }
}

function _private_DragDrop_InvalidDrop(_oDropContext)
{
    // DragDrop_RestoreOriginalPosition();
    
    // Valid drop context ?
    if(_oDropContext != null)
    {
        // Valid draged list ?
        if((_oDropContext.aoSourceElementList != null)
        && (_oDropContext.aoSourceElementList.length > 0))
        {
            // Remove all elements
            for(var i=0; i<_oDropContext.aoSourceElementList.length; i++)
            {
                var oElement = document.getElementById(_oDropContext.aoSourceElementList[i]);
                
                // Valid ?
                if((oElement != null)
                && (oElement.parentNode != null))
                {
//                    oElement.parentNode.removeChild(oElement);                    
                }
            }
        }
    }
}

/**
 * Drop a component
 */
function DragDrop_Drop()
{
    //Release mouse function
    Event_RemoveAction(EVENT_MOUSE_MOVE, DragDrop_Move);
    Event_RemoveAction(EVENT_MOUSE_MOVE, _private_DragDrop_MouseMove);
    Event_RemoveAction(EVENT_MOUSE_UP, DragDrop_Drop);
    gbBubble = true;

    // Creates context
    var oDropContext = new DragDrop_Context();

    // update cursor
    document.documentElement.style.cursor = "auto";

    // There is a valid drop, make the context
    if(DragDrop_goDragElementList != null)
    {
        // Loop on list
        for(var sKey in DragDrop_goDragElementList)
        {
            // Get drag element
            var oDragElement = DragDrop_goDragElementList[sKey].oElement;
            
            // Popup ?
            if((DragDrop_goDragElementList[sKey].xFlags & DRAGDROP_POPUP) != DRAGDROP_POPUP)
            {/*
            var oNode = document.createTextNode(DragDrop_goDragElementList[sKey].xFlags);
            var oNode2 = document.createElement("br");
            document.getElementsByTagName("BODY")[0].appendChild(oNode);
            document.getElementsByTagName("BODY")[0].appendChild(oNode2);
            */
                // Copy table
                oDropContext.aoSourceElementList.push(oDragElement.id);
                
                // Remove the draging class
                // Copy ?
                if(Tools_IsCopy(oDragElement))
                {
                    // Remove the draging copy class
                    Tools_RemoveClass(oDragElement, DRAGDROP_DRAGINGCOPY_CLASS);
                    
                    // Gets the Original element
                    oDragElement = document.getElementById(Tools_RemoveCopySuffix(oDragElement.id));
                }
                
                // Valid element ?
                if(oDragElement != null)
                {
                    // Remove the Drag class
                    Tools_RemoveClass(oDragElement, DRAGDROP_DRAGINGORIGINAL_CLASS);
                }
            }
        }
    }
/*    
    // Remove a class on all the target elements
    for(var i=0; i<DragDrop_gasDropableElementList.length; i++)
    {
        // Gets control
        var oDropTarget = document.getElementById(DragDrop_gasDropableElementList[i]);
        
        // Valid ?
        if(oDropTarget != null)
        {
            Tools_RemoveClass(oDropTarget, DRAGDROP_DROPSELECT_CLASS);
        }
    }
*/
    
    // Release the dragged element list
    DragDrop_goDragElementList = null;

    // Is there a valid target?
    if(DragDrop_goTarget != null)
    {
        // Set target
        oDropContext.oTargetElement = DragDrop_goTarget.id;
        
        // Remove the target class
        Tools_RemoveClass(oDropContext.oTargetElement, DRAGDROP_DROPOVER_CLASS);

        // Send a valid drop event
        _private_DragDrop_ValidDrop(oDropContext);
    }
/*
    else
    {
        // Restoring process not already running ?
        if(!DragDrop_gbRestoringRun)
        {
            // Move back the element to its original location
            _private_DragDrop_InvalidDrop(oDropContext);
        }
    }
*/

    // Remove elements (copy)
    // Valid drop context ?
    if(oDropContext != null)
    {
        // Valid draged list ?
        if((oDropContext.aoSourceElementList != null)
        && (oDropContext.aoSourceElementList.length > 0))
        {
            // Remove all elements
            for(var i=0; i<oDropContext.aoSourceElementList.length; i++)
            {
/*
            var oNode = document.createTextNode(oDropContext.aoSourceElementList[i]);
            var oNode2 = document.createElement("br");
            document.getElementsByTagName("BODY")[0].appendChild(oNode);
            document.getElementsByTagName("BODY")[0].appendChild(oNode2);
*/
                var oElement = document.getElementById(oDropContext.aoSourceElementList[i]);
                
                // Valid ?
                if((oElement != null)
                && (oElement.parentNode != null)
                && (Tools_IsCopy(oElement)))
                {
                    oElement.parentNode.removeChild(oElement);
                }
            }
        }
    }
}

