/** jquery.dragable&resizeable&dropable plugin 
 * jquery.dimensions required
 * 
 * required parameter: elem - whatever that might be given to jquery / only if called directly: new dynamic(elem, options)
 * 
 * optional parameters:  $('#object').dragable({params}).resizeable({params});
 * ----------------------
 * d - enable dragging // only if called directly
 * r - enable resizing // only if called directly
 * direction, resizeDirection - 'h' or 'v'
 * noevents - do not bind event handlers (for manual use)
 * edge - {x1, x2, y1, y2}
 * start, move, end - dragable callback functions( o ) // start(o, dom event), move(o, dom event)
 * over, moveover, out, drop - dropable target callback functions( o , target jquery object )
 * resizeStart, resize, resizeEnd - resizeable callback functions( o )
 * handlerPosition - custom function(o, handler jquery object) for setting the resize handler position
 * moveHandler - move handler object / whatever that might be given to $()
 * moveHandlerOutside - bool (false by default) - tells plugin to look moveHandler (jquery selector) outside of elem object (useful with mass dragging)
 * / if not set, plugin will look inside of elem - $(moveHandler, o.elem)
 * target - collection of target objects for dropping / whatever that might be given to $()
 * renewTarget - target collection should be renewed before moving
 * tolerance - 'intersect', 'leftcorn', 'pointer' - the way two object should intersect / todo: more modes
 * clone - bool or callback(o, new jquery object) - do not drag the actual object, use clone 
 * cloneRemove - callback(o, cloned jquery object) - called before object.remove(); 
 * / if returns false, object.remove() will not be called and also the position of the original element will not be updated
 * / useful for drag'n'drop creation of objects
 * moveReplacer - jquery object / can be used instead of clone / if both set, plugin will choose replacer 
 * / clone and cloneRemove callbacks, if set, will be called too with the same logic
 * / moveReplacer will not be removed from dom anyway
 * opacity, initialOpacity - (string) double - opacity of dragged object ( default element, cloned element, or replacer element - no matter)
 * zIndex - if set, it will be used while dragging, and initial z-index from style will be restored on mouseup event
 * proportion, min, max - {w, h}
 * resizeHandlerClass - initial class for resize handler
 * parent - jquery object of parent element / if edge is not set, it will be taken from parent element
 * 
 * -- in development
 * autoscroll - bool - will be used only if parent element is set
 * scrollStep - int / 15 by default
 * scrollMargin - int / 30 by default
 * -----------------------
 * 
 * simple use:
 * $('#object').dragable().resizeable();
 * 
 * @author: johann kuindji (www.kuindji.com , www.stuffedguys.com)
 * @email: jk@kuindji.com
 */

(function($){
$.fn.extend({
    dragable: function(options) {
        if (!this[0]) return this;
        if (!options) options = {};
        for (var j = 0; j < this.length; j++) {
            if (!this[j].dynamic) {
                var options = $.fn.extend({d: true}, options);
                this[j].dynamic = new dynamic(this[j], options);
            }
            else {
                this[j].dynamic.applyOptions(options);
                this[j].dynamic.dragable();
            }
        }
        return this;
    },

    notDragable: function() {
        if (!this[0]) return this;
        for (var j = 0; j < this.length; j++) if (this[j].dynamic) this[j].dynamic.notDragable();
        return this;
    },

    resizeable: function(options) {
        if (!this[0]) return this;
        if (!options) options = {};
        for (var j = 0; j < this.length; j++) {
            if (!this[j].dynamic) {
                var options = $.fn.extend({r: true}, options);
                this[j].dynamic = new dynamic(this[j], options);
            }
            else {
                this[j].dynamic.applyOptions(options);
                this[j].dynamic.resizeable();
            }
        }
        return this;
    },

    notResizeable: function() {
        if (!this[0]) return this;
        for (var j = 0; j < this.length; j++) if (this[j].dynamic) this[j].dynamic.notResizeable();
        return this;
    },

    drag: function(ev) {
        if (!this[0]) return this;
        for (var j = 0; j < this.length; j++) if (this[j].dynamic) this[j].dynamic.mousedown(ev);
        return this;
    },

    stopDrag: function() {
        if (!this[0]) return this;
        for (var j = 0; j < this.length; j++) if (this[j].dynamic) this[j].dynamic._dragable = false;
        return this;    
    },

    dynamic: function() {
        if (!this[0] || !this[0].dynamic) return {};
        return this[0].dynamic;
    }
})})(jQuery);

function dynamic(elem, options) {
    if (!options) options = {};
    options.elem = elem;
    this.elem = null;
    this.originalElem = null;
    this.moveHandler = null;
    this.resizeHandler = null;
    this.target = null;
    this.d = false;
    this.r = false;

    this.edge = options.edge || null;
    this.parent = options.parent || null;
    this.autoscroll =  options.parent ? options.autoscroll || null : null;
    this.renewTarget = options.renewTarget || false;
    this.scrollStep = options.scrollStep || 15;
    this.scrollMargin = options.scrollMargin || 30;
    this.min = options.min || null;
    this.max = options.max || null;
    this.proportion = options.proportion || null;
    this.direction = options.direction || null;
    this.resizeDirection = options.resizeDirection || null;
    this.noevents = options.noevents || null;
    this.zIndex = options.zIndex || null;
    this.tolerance= options.tolerance || 'intersect';
    this.clone = options.clone || false;
    this.cloneRemove = options.cloneRemove || null;
    this.moveReplacer = options.moveReplacer || null;
    this.opacity = options.opacity || null;
    this.initialOpacity = options.initialOpacity || null;

    this.start = options.start || null;
    this.move = options.move || null;
    this.end = options.end || null;

    this.over = options.over || null;
    this.moveover = options.moveover || null;
    this.out = options.out || null;
    this.drop = options.drop || null;

    this.resizeStart = options.resizeStart || null;
    this.resize = options.move || null;
    this.handlerPosition = options.handlerPosition || null;
    this.resizeEnd = options.resizeEnd || null;

    this.options = options;
    this._dragable =false;
    this._resizeable = false;
    this.prevX = 0;
    this.prevY = 0;
    this.left = 0;
    this.top = 0;
    this.w = 0;
    this.h = 0;
    this.prevRX = 0;
    this.prevRY =0;
    this.initialIndex = null;
    this.targetMatrix = null;
    this.currentTarget = null;
    this.prevPosition = null;
    this.prevOpacity = null;
    this.bodyParent = false;

    extendDynamic(this);
}

function extendDynamic(o) {

    o.applyOptions = function(options) {
        if (!options) return false;
        for (var i in options) o[i]=options[i];
        o.options = $.extend(o.options, options);
    }

    // common functions
    o.setPosition = function( anyway ) {
        if ( !o.direction || anyway ) return o.elem.css({left: o.left, top: o.top});
        if ( o.direction == 'h' ) return o.elem.css('left', o.left);
        if ( o.direction == 'v' ) return o.elem.css('top', o.top);
    }

    o.checkPosition = function() {
        if (!o.edge) return false;
        var shifted = false;
        if ( o.autoscroll && o.parent ) { // not finished yet
            if ( o.left < o.edge.x1 + o.scrollMargin ) {
                o.parent.get(0).scrollLeft -= o.scrollStep;
                if (o.bodyParent) { o.edge.x1 -= o.scrollStep; o.edge.x2 -= o.scrollStep; }
            }
            if ( o.top < o.edge.y1 +  o.scrollMargin ) {
                o.parent.get(0).scrollTop -= o.scrollStep;
                if (o.bodyParent) { o.edge.y1 -= o.scrollStep; o.edge.y2 -= o.scrollStep; }
            }
            if ( o.left + o.w > o.edge.x2 - o.scrollMargin ) {
                o.parent.get(0).scrollLeft += o.scrollStep;
                if (o.bodyParent) { o.edge.x2 += o.scrollStep; o.edge.x1 += o.scrollStep; }
            }
            if ( o.top + o.h > o.edge.y2 -  o.scrollMargin ) {
                o.parent.get(0).scrollTop += o.scrollStep;
                if (o.bodyParent) { o.edge.y2 += o.scrollStep; o.edge.y1 += o.scrollStep; }
            } 
            if (o.edge.x1 < 0) o.edge.x1 = 0;
            if (o.edge.x2 < 0) o.edge.x2 = 0;
            if (o.edge.y1 < 0) o.edge.y1 = 0;
            if (o.edge.y2 < 0) o.edge.y2 = 0;
        }
        if ( o.left < o.edge.x1 ) { o.left = o.edge.x1; shifted = true; }
        if ( o.top < o.edge.y1 ) { o.top = o.edge.y1; shifted = true; }
        if ( o.left + o.w > o.edge.x2 ) { o.left = o.edge.x2 - o.w; shifted = true; }
        if ( o.top + o.h > o.edge.y2 ) {o.top = o.edge.y2 - o.h; shifted = true; }
        return shifted;
    }

    o.updatePosition = function() {
        var ofs = o.elem.offset();
        o.left = ofs.left;
        o.top = ofs.top;
        if (o.edge) { o.updateSize(); o.setPosition( o.checkPosition() ); }
        else o.setPosition();
        return true;
    }

    o.updateSize = function() {
        o.w = o.elem.width();
        o.h = o.elem.height();
    }

    o.setSize = function(w, h) {
        var corrected = false;
        if (!w || w < 1) w = 1;
        if (!h || h < 1) h = 1;
        if (o.min) {
            if ( w < o.min.w ) {w = o.min.w; corrected = true; }
            if ( h < o.min.h) {h = o.min.h; corrected = true; }
        }
        if (o.max) {
            if ( w > o.max.w ) {w = o.max.w; corrected = true; }
            if ( h > o.max.h ) {h = o.max.h; corrected = true; }           
        }        
        if (o.proportion) {
            if (w >= h) {
                var h1 = Math.ceil( (w*o.proportion.h)/o.proportion.w )
                if (h1 != h) correted = true;
                h = h1;
            }
            if (h > w ) {
                var w1 = Math.ceil( (h*o.proportion.w)/o.proportion.h )
                if (w1 != w) corrected = true;
                w = w1;
            }
        }
        if (o.edge) {
            if ( o.left + w > o.edge.x2 ) { w = o.edge.x2 - o.left; corrected = true; }
            if ( o.top + h > o.edge.y2 ) { h = o.edge.y2 - o.top; corrected = true; }
        }
        o.w = w;
        o.h = h;
        o.elem.css({width: o.w, height: o.h});
        if (corrected && o.edge && o.checkPosition()) o.setPosition();
        return corrected;
    }

    // drop functions
    o.prepareTargetMatrix = function() {
        o.targetMatrix = [];
        o.target.each ( function() {
            var t = $(this);
            var ofs = t.offset();
            var w = t.width();
            var h = t.height();
            this.targetPosition = {x1: ofs.left, x2: ofs.left+w, y1: ofs.top, y2: ofs.top+h, t: this};
            var inx = o.targetMatrix.push(this.targetPosition);
        })
        if (o.targetMatrix.length == 0) o.targetMatrix = null;
    }

    o.intersect = function(coord) {
        if (coord.t == o.elem[0] || (o.originalElem && coord.t == o.originalElem[0]) || (coord.t.dynamic && coord.t.dynamic._dragable)) return false;
        switch (o.tolerance ) {
        case 'intersect': 
            if ( o.left+o.w < coord.x1 || o.left > coord.x2 ) return false;
            if ( o.top+o.h < coord.y1 || o.top > coord.y2 ) return false;
            return true;
        case 'leftcorn': 
            //if ( o.left > coord.x1 && o.left < coord.x2 && o.top > coord.y1 && o.top < coord.y2 ) return true;
            if ( o.top > coord.y1 - 5 && o.top < coord.y2 + 5 ) return true;
            return false;
        case 'pointer':
            if ( o.prevX >= coord.x1 && o.prevX <= coord.x2 && o.prevY >= coord.y1 && o.prevY <= coord.y2 ) return true;
            return false;
        } 
        return false;
    }

    o.checkTargetMatrix = function(ev) {
        if (!o.targetMatrix) return false;
        var e = o.currentTarget ? o.currentTarget.get(0) : false;
        if (o.currentTarget) { //check if still over the object
            if (o.currentTarget.get(0).targetPosition) {
                if ( !o.intersect(o.currentTarget.get(0).targetPosition)) o.currentTarget = null;
            }
            else o.currentTarget = null;
        }
        if (!o.currentTarget) { 
            if (e && o.out) o.out (o, $(e));
            for (var i = 0; i < o.targetMatrix.length; i++) if (o.intersect(o.targetMatrix[i])) o.currentTarget = $(o.targetMatrix[i].t);
            if (o.currentTarget && o.over) o.over(o, o.currentTarget);
        }
    }

    // move functions
    o.createClone = function(x, y) {
        o.prevPosition = 'absolute';
        o.originalElem = o.elem;
        if (!o.moveReplacer) {
            o.elem = o.elem.clone();
            o.elem.hide().appendTo('body').css({position: 'absolute', left: o.left, top: o.top, width: o.elem.width(), 'z-index' : 10000, 'zIndex' : 10000}).show();
        }
        else {
            o.elem = o.moveReplacer;
            o.left = x - Math.ceil(o.elem.width()/2);
            o.top = y - Math.ceil(o.elem.height()/2);
            o.elem.css({position: 'absolute', left: o.left, top: o.top}).show();
        }
        if ( typeof(o.clone) == 'function') o.clone(o, o.elem);
    }

    o.removeClone = function() {
        if (!o.originalElem) return false;
        if (o.cloneRemove && !o.cloneRemove(o, o.elem) ) {
            o.elem = o.originalElem;
            o.originalElem = null;
            return false;
        }
        o.originalElem.css({left: o.left, top: o.top})
        if (!o.moveReplacer) o.elem.remove();
        else o.elem.hide();
        o.elem = o.originalElem;
        o.originalElem = null;
        o.updatePosition();
    }

    o.mousedown = function (ev) {
        el = (ev.target) ? ev.target : ((ev.srcElement) ? ev.srcElement : null);
        if (/^a$/i.test(el.tagName)) return false
        if (o._dragable) return false;
        o.prevX = ev.clientX+document.body.scrollLeft;
        o.prevY = ev.clientY+document.body.scrollTop;
        var ofs = o.elem.offset();
        o.left = ofs.left;
        o.top = ofs.top;
        //if ((o.elem.hasClass('st_a') && (o.prevX < o.left + 27))) return false; // IW380
        if ((o.elem.hasClass('st_a') && (o.prevX > o.left + o.elem.width() - 25))) return false; // IW380
        if ( o.clone || o.moveReplacer ) o.createClone(o.prevX, o.prevY);
        if ( o.zIndex ) {
            o.initialIndex = o.elem.css('z-index')  ||  1;
            o.elem.css('z-index', o.zIndex);
        }
        o._dragable = true;
        if (o.opacity) o.elem.css('opacity', o.opacity);
        if (o.renewTarget) o.target = $(o.options.target);
        if (o.target && o.target.length > 0) o.prepareTargetMatrix();
        if (o.resizeHandler) o.toggleHandler( false );
        if (!o.prevPosition) o.prevPosition = o.elem.css('position');
        if (o.prevPosition != 'absolute') o.elem.css({position: 'absolute', left: o.left, top: o.top})
        if (o.start) o.start(o, ev);
        return false;
    }

    o.onmove = function(ev) {
        if (!o._dragable) return false;
        var mx = ev.clientX+document.body.scrollLeft;
        var my = ev.clientY+document.body.scrollTop;
        o.left += mx - o.prevX;
        o.top += my - o.prevY;
        o.prevX = mx;
        o.prevY = my;
        if (o.edge) o.checkPosition();
        o.setPosition();
        if (o.targetMatrix) o.checkTargetMatrix();
        if (o.move) o.move(o, ev);
        if (o.targetMatrix && o.currentTarget && o.moveover) o.moveover( o, o.currentTarget , ev);
        return true;
    }

    o.onmoveend = function() {
        if (!o._dragable) return false;
        o._dragable = false;
        if (o.clone || o.moveReplacer) o.removeClone();
        if (o.opacity && !o.clone) o.elem.css('opacity', o.initialOpacity ? o.initialOpacity : '1');
        if (o.initialIndex) o.elem.css('z-index', o.initialIndex);
        if (o.resizeHandler) {
            o.setHandlerPosition();
            o.toggleHandler( true );
        } 
        if (o.currentTarget && o.drop) o.drop(o, o.currentTarget);
        o.currentTarget = null;
        if (o.prevPosition != 'absolute') o.elem.css('position', o.prevPosition);
        if (o.end) o.end(o);
        return false;
    }

    // resize functions
    o.onResizeStart = function() {
        if (o._resizeable) return false;
        o.prevW = o.w;
        o.prevH = o.h;
        o.prevRX = o.resizeHandler.prevX;
        o.prevRY = o.resizeHandler.prevY;
        if (o.resizeStart) o.resizeStart(o);
        o._resizeable = true;
        return false;
    }

    o.onResizeMove = function() {
        if (!o._resizeable) return false;
        var w = o.w;
        var h = o.h;
        if ( o.resizeDirection != 'h') w += o.resizeHandler.prevX - o.prevRX;
        if ( o.resizeDirection != 'v') h += o.resizeHandler.prevY - o.prevRY;
        o.prevRX = o.resizeHandler.prevX;
        o.prevRY = o.resizeHandler.prevY;
        o.setSize(w, h);
        o.setHandlerPosition();
        if (o.targetMatrix) o.checkTargetMatrix();
        if (o.resize) o.resize(o);
        if (o.targetMatrix && o.currentTarget && o.moveover) o.moveover( o, o.currentTarget );
        return false;
    }

    o.onResizeEnd = function() {
        if (o.resizeEnd) o.resizeEnd(o);
        o._resizeable = false;
        return false;
    }

    o.setHandlerPosition = function() {
        if (o.handlerPosition) return o.handlerPosition( o, o.resizeHandler );
        o.resizeHandler.elem.css({left: (o.left + (o.w - Math.ceil(o.resizeHandler.w/2))), top: ( o.top + (o.h - Math.ceil(o.resizeHandler.h/2)))  });
    }

    o.toggleHandler = function(state) { o.resizeHandler.elem[ state? 'show':'hide' ](); }

    // init
    o.element = function(elem, parent) {
        if ( typeof(elem) == 'undefined') return null;
        if ( typeof(elem) == 'string') return $(elem, parent);
        if (elem.jquery) return elem;
        return $(elem);
    }

    o.dragable = function() {
        $( function() {
        if (o.d) return false;
        o.moveHandler =  o.options.moveHandler ? o.element(o.options.moveHandler, o.options.moveHandlerOutside ? null : o.elem) : o.elem;
        o.moveHandler.bind('mousedown', o.mousedown);
        $(o.elem.get(0).ownerDocument).bind('mousemove', o.onmove);
        $(o.elem.get(0).ownerDocument).bind('mouseup', o.onmoveend);
        if (!o.autoscroll) $(o.elem.get(0).ownerDocument).bind('scroll', o.onmoveend);
        if (o.parent && !o.edge) {
            if (o.parent.get(0).tagName.toLowerCase()=='body') {
                o.edge = {x1:0, x2: document.body.clientWidth, y1: 0, y2: document.body.clientHeight};
                o.bodyParent = true;
            }
            else {
                var ofs = o.parent.offset();
                o.edge = {x1: ofs.left, x2: ofs.left+o.parent.width(), y1: ofs.top, y2: ofs.top+o.parent.height()};
            }
        }
        o.d = true;
        }) 
    }

    o.notDragable = function() {
        if (!o.d) return false;
        o.moveHandler.unbind('mousedown', o.mousedown);
        $(o.elem.get(0).ownerDocument).unbind('mousemove', o.onmove);
        $(o.elem.get(0).ownerDocument).unbind('mouseup', o.onmoveend);
        $(o.elem.get(0).ownerDocument).unbind('scroll', o.onmoveend);
        o.target = null;
        o.moveHandler = null;
        o.d = false;
    }

    o.resizeable = function() {
        $(function() {
        if (o.r) return false;
        o.resizeHandler = new dynamic($('<div></div>')[0], {edge: o.edge, start: o.onResizeStart, move: o.onResizeMove, end:o.onResizeEnd, 
                                                                                                       noevents: o.noevents, direction: o.resizeDirection, d: true});
        $('body').append(o.resizeHandler.elem);
        if (o.options.resizeHandlerClass) o.resizeHandler.elem.addClass(o.options.resizeHandlerClass);
        else o.resizeHandler.elem.css({backgroundColor: '#ffffff', width: '6px', height: '6px', 
                                                    position: 'absolute', cursor: 'nw-resize', zIndex: '999', borderWidth:'1px', borderColor: '#000', borderStyle: 'solid'});
        o.resizeHandler.updateSize();
        o.resizeHandler.checkPosition();
        o.setSize(o.w, o.h);
        o.setHandlerPosition();
        o.r = true;
        })
    }

    o.notResizeable = function() {
        if (!o.r) return false;
        o.resizeHandler.notDragable();
        o.resizeHandler.remove();
        o.resizeHandler = null;
        o.r = false;
    }

    $(function() {
        o.elem =  o.element( o.options.elem );
        o.updateSize();
        o.updatePosition();
        if ( o.noevents ) return false;
        if ( typeof (o.elem.get(0) ) == 'undefined') return false;
        o.target =  o.options.target ?  $(o.options.target) : null;
        if (o.options.d) o.dragable();
        if (o.options.r) o.resizeable();
    })
}
