/******************************************************
* DRAGABLE BOXES
******************************************************/

var oBox            = null;         // assigned as the object currently being dragged
var offsetX, offsetY;               // offset from top left corner to where mouse was clicked
var nZIndex         = 1;            // z-index for the object currently being dragged 
var bDropZones      = false;        // set true if dropzones are registered
var oDropZones      = new Object(); // object to hold registered drop zones
var oActiveDropZone = null;         // current dropzone that is mouseovered
var sInitialClass   = null;         // holds the initial class name of mouseovered dropzone

/**
 * Register a drop zone by id and give the name of the handler callback function
 * @param       string      id of dragged object
 * @param       string      callback function name
 */
function registerDropZone (sId, sDropHandler)
{
    if (!bDropZones) bDropZones = true;
    oDropZones[sId] = sDropHandler;
}

/**
 * return dragged object to it's starting position
 */
function returnObject ()
{
    oBox.style.left = oBox.xStart + 'px';
    oBox.style.top  = oBox.yStart + 'px';
}

/**
 * change the class of an object
 * @param      obj         object to change class on
 * @param      string      new classname to apply
 */
function changeClass (obj, sClass)
{
    obj.className = sClass;
}
    
/**
 * start the drag event, set opacity, grab current location, set up event listeners
 * and disable text selection for IE.
 * This sets the global oBox and offsetX/offsetY variables. This was needed because
 * reading these each time was too slow and caused problems with dragging.
 * @param      object      event object
 */
function dragPress(evt, bBound)
{
    var oEvent = new DragEvent(evt);
    oBox = oEvent.source;
    oBox.style.zIndex = nZIndex++;  // make the newly dragged box top layer.
    oBox.xStart = parseInt(oBox.style.left);
    oBox.yStart = parseInt(oBox.style.top);
    oBox.bBound = bBound || false;
    setOpacity(oBox, .6);
    offsetX = oEvent.x - getX(oBox);
    offsetY = oEvent.y - getY(oBox);
    addEventListener(document, 'mousemove', dragMove, false);
    addEventListener(document, 'mouseup', dragRelease, false);
    document.body.ondrag = function () { return false; };
    document.body.onselectstart = function () { return false; };
}

/**
 * Start new drag event and set the new location based on the global offsets, also
 * after each move eat the event so you don't get flickering.
 * @param      object      event object 
 */
function dragMove(evt)
{
    var oEvent = new DragEvent(evt);
    setX(oBox, oEvent.x, offsetX);
    setY(oBox, oEvent.y, offsetY);
    if (bDropZones) oEvent.checkMouseOver();
    oEvent.eat();
}

/**
 * Start new drag event set opacity back to full and remove event listeners
 * also re-enable text selection in IE
 * @param      object      event object 
 */
function dragRelease(evt)
{
    var oEvent = new DragEvent(evt);
    setOpacity(oBox, 1);
    removeEventListener(document, 'mousemove', dragMove, false);
    removeEventListener(document, 'mouseup', dragRelease, false);
    document.body.ondrag = function () { return true; };
    document.body.onselectstart = function () { return true };
    if (bDropZones) oEvent.checkDropZones();
    if (oActiveDropZone) oEvent.clearActiveDropZone(sInitialClass);
}

/**
 * Drag event class   
 * @param      object      event object
 * @param      boolean     is the object bound to only move to dropzones,
 *                         or can it be dropped anywhere ?
 */    
function DragEvent(evt)
{
    var self    = this;                                       
    this.evt    = evt ? evt : window.event;
    this.source = evt.target ? evt.target : evt.srcElement;
    this.id     = this.source.id;
    this.x      = evt.pageX  ? evt.pageX : evt.clientX;
    this.y      = evt.pageY  ? evt.pageY : evt.clientY;
    
    /**
     * return the event stats for debugging
     */
    this.debug = function () 
    {
        return 'Event [ x => ' + this.x + ', y => ' + this.y + ' ]';
    }
    
    /**
     * Called when an object is dropped. It checks if it was dropped over a 
     * registered drop zone.
     */
    this.checkDropZones = function ()
    {
        var sDropped = self.getDropZone();
        if (sDropped)
        {
            self.activateDropZone(sDropped, self.id); 
        }
        else
        {
            // no dropzones were triggered and if bBound is set put the object back where it came from
            if (oBox.bBound) returnObject();
        }
    }
    
    this.checkMouseOver = function ()
    {
        var sMouseOver = self.getDropZone();
        // okay we are over a drop zone
        if (sMouseOver)
        {
            var oMouseOver = document.getElementById(sMouseOver);
            /* tricky here, if there isn't an initalclass then set it... (first mouseover)
             * also make sure you didn't quickly drag from one drop zone to another and skip the event  
             * had problems with currently active dropzone class coping to the next active dropzone if mouse drag was too fast
             * so basically if the class name is different or not set do special stuff
             */
            if (!sInitialClass || (sInitialClass != oMouseOver.className && sInitialClass != oMouseOver.className.replace(/_active/, '')))
            {
                if (oActiveDropZone) changeClass(oActiveDropZone, sInitialClass);
                sInitialClass = oMouseOver.className;
            }
            // set the classes only if they will be different than current
            if (oActiveDropZone && oMouseOver.className != sInitialClass) changeClass(oActiveDropZone, sInitialClass);
            if (oMouseOver.className != sInitialClass + '_active') changeClass(oMouseOver, sInitialClass + '_active');             
            oActiveDropZone = oMouseOver;
            return;
        }    
        // if not over a drop zone clear the active drop zone if there is one.
        if (oActiveDropZone) this.clearActiveDropZone(sInitialClass);
    }
    
    this.getDropZone = function ()
    {
        // loop through registered drop zones to determine if we are over them
        for (var key in oDropZones)
        {        
            oDropped = document.getElementById(key);
            x      = getX(oDropped);
            y      = getY(oDropped);
            width  = getWidth(oDropped);
            height = getHeight(oDropped);
            // alert(x + '.' + y + '.' + width + '.' + height);
            // alert(self.x + ' > ' + x + ' && ' + self.y + ' > ' + y + ' && ' + self.x + ' < ' + (x + width) + ' && ' + self.y + ' < ' + (y + height));
            if (self.x > x && self.y > y && self.x < x + width && self.y < y + height)
            {
                // if over a drop zone activate that dropzones callback handler
                return key;
            }    
        }
    }
    
    /**
     * Activate the dropzones callback handler
     * @param        string       id of the dropzone that was activated
     * @param        string       id of the dropped object
     */  				
    this.activateDropZone = function (sDropZone, sDropped)
    {
        eval(oDropZones[sDropZone] + '(\'' + sDropped + '\');');
    }    
    
    this.clearActiveDropZone = function (sClass)
    {    
        changeClass(oActiveDropZone, sClass);
        oActiveDropZone = null;
        sInitialClass   = null;
    }    
    
    /**
     * Stop the event propagation
     */
    this.eat = function ()
    {
        // mozilla
        if (self.stopPropagation)
        {
            self.stopPropagation();
            self.preventDefault();
        } 
        // IE
        else if (this.evt.cancelBubble)
        {
            self.cancelBubble = true;
            self.returnValue  = false;
        }
    }
}      

/**
 * set opacity
 * @param      object     object to make set transparency on
 * @param      number     .1 - 1
 */      
function setOpacity (obj, val)
{
    // IE
    if (obj.filters)
    {
        try 
        {
            obj.filters['alpha'].opacity = val * 100;
        } 
        catch (e) { }
    } 
    // others
    else if (obj.style.opacity)
    {
        obj.style.opacity = val;
    }
}     
     
/**
 * Get the x position
 * @param      object       object to get the x postion of
 */      
function getX (obj)
{
    return parseInt(obj.style.left);
}

/**
 * Get the y position
 * @param      object       object to get the y postion of     
 */      
function getY (obj)
{
    return parseInt(obj.style.top);
}     
  
/**
 * set the x position of the clicked object
 * @param      object      object to set on
 * @param      int         offset amount between edge and distance clicked from edge
 */            
function setX (obj, x, offset)
{
    obj.style.left = (x - offset) + 'px';
}

/**
 * set the y position of the clicked object
 * @param      object      object to set on
 * @param      int         offset amount between top and distance clicked from top
 */       
function setY (obj, y, offset)
{
    obj.style.top = (y - offset) + 'px';
}              

/**
 * Get the width 
 * @param      object       object to get the width of
 */
function getWidth (obj)
{
    return parseInt(obj.style.width);
}

/**
 * Get the height 
 * @param      object       object to get the height of
 */    
function getHeight (obj)
{
    return parseInt(obj.style.height);
}  
