/**
 * A simple class that provides the basic implementation needed to make any element draggable.
 */
Ext.define('Ext.dd.DragSource', {
    extend: 'Ext.dd.DDProxy',
    requires: [
        'Ext.dd.StatusProxy',
        'Ext.dd.DragDropManager'
    ],
 
    /**
     * @cfg {String} ddGroup
     * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
     * interact with other drag drop objects in the same group.
     */
 
    /**
     * @property {Object} dragData
     * This property contains the data representing the dragged object. This data is set up by the implementation of the
     * {@link #getDragData} method. It must contain a ddel property, but can contain any other data according to the
     * application's needs.
     */
 
    /**
     * @cfg {String} dropAllowed
     * The CSS class returned to the drag source when drop is allowed.
     */
    dropAllowed : Ext.baseCSSPrefix + 'dd-drop-ok',
    /**
     * @cfg {String} dropNotAllowed
     * The CSS class returned to the drag source when drop is not allowed.
     */
    dropNotAllowed : Ext.baseCSSPrefix + 'dd-drop-nodrop',
 
    /**
     * @cfg {Boolean} animRepair
     * If true, animates the proxy element back to the position of the handle element used to trigger the drag.
     */
    animRepair: true,
 
    /**
     * @cfg {String} repairHighlightColor
     * The color to use when visually highlighting the drag source in the afterRepair
     * method after a failed drop (defaults to light blue). The color must be a 6 digit hex value, without
     * a preceding '#'.
     */
    repairHighlightColor: 'c3daf9',
 
    /**
     * Creates new drag-source.
     * @param {String/HTMLElement/Ext.dom.Element} el The container element or ID of it.
     * @param {Object} config (optional) Config object.
     */
    constructor: function(el, config) {
        this.el = Ext.get(el);
        if(!this.dragData){
            this.dragData = {};
        }
 
        Ext.apply(this, config);
 
        if(!this.proxy){
            this.proxy = new Ext.dd.StatusProxy({
                id: this.el.id + '-drag-status-proxy',
                animRepair: this.animRepair
            });
        }
        this.callParent([this.el.dom, this.ddGroup || this.group,
              {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true}]);
 
        this.dragging = false;
    },
 
    /**
     * Returns the data object associated with this drag source
     * @return {Object} data An object containing arbitrary data
     */
    getDragData : function(e){
        return this.dragData;
    },
 
    /**
     * @private
     */
    onDragEnter : function(e, id){
        var target = Ext.dd.DragDropManager.getDDById(id),
            status;
        this.cachedTarget = target;
        if (this.beforeDragEnter(target, e, id) !== false) {
            if (target.isNotifyTarget) {
                status = target.notifyEnter(this, e, this.dragData);
                this.proxy.setStatus(status);
            } else {
                this.proxy.setStatus(this.dropAllowed);
            }
 
            if (this.afterDragEnter) {
                /**
                 * An empty function by default, but provided so that you can perform a custom action
                 * when the dragged item enters the drop target by providing an implementation.
                 * @param {Ext.dd.DragDrop} target The drop target
                 * @param {Event} e The event object
                 * @param {String} id The id of the dragged element
                 * @method afterDragEnter
                 */
                this.afterDragEnter(target, e, id);
            }
        }
    },
 
    /**
     * An empty function by default, but provided so that you can perform a custom action
     * before the dragged item enters the drop target and optionally cancel the onDragEnter.
     * @param {Ext.dd.DragDrop} target The drop target
     * @param {Event} e The event object
     * @param {String} id The id of the dragged element
     * @return {Boolean} isValid True if the drag event is valid, else false to cancel
     * @template
     */
    beforeDragEnter: function(target, e, id) {
        return true;
    },
 
    /**
     * @private
     */
    onDragOver: function(e, id) {
        var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id),
            status;
        if (this.beforeDragOver(target, e, id) !== false) {
            if(target.isNotifyTarget){
                status = target.notifyOver(this, e, this.dragData);
                this.proxy.setStatus(status);
            }
 
            if (this.afterDragOver) {
                /**
                 * An empty function by default, but provided so that you can perform a custom action
                 * while the dragged item is over the drop target by providing an implementation.
                 * @param {Ext.dd.DragDrop} target The drop target
                 * @param {Event} e The event object
                 * @param {String} id The id of the dragged element
                 * @method afterDragOver
                 */
                this.afterDragOver(target, e, id);
            }
        }
    },
 
    /**
     * An empty function by default, but provided so that you can perform a custom action
     * while the dragged item is over the drop target and optionally cancel the onDragOver.
     * @param {Ext.dd.DragDrop} target The drop target
     * @param {Event} e The event object
     * @param {String} id The id of the dragged element
     * @return {Boolean} isValid True if the drag event is valid, else false to cancel
     * @template
     */
    beforeDragOver: function(target, e, id) {
        return true;
    },
 
    /**
     * @private
     */
    onDragOut: function(e, id) {
        var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
        if (this.beforeDragOut(target, e, id) !== false) {
            if (target.isNotifyTarget) {
                target.notifyOut(this, e, this.dragData);
            }
            this.proxy.reset();
            if (this.afterDragOut) {
                /**
                 * An empty function by default, but provided so that you can perform a custom action
                 * after the dragged item is dragged out of the target without dropping.
                 * @param {Ext.dd.DragDrop} target The drop target
                 * @param {Event} e The event object
                 * @param {String} id The id of the dragged element
                 * @method afterDragOut
                 */
                this.afterDragOut(target, e, id);
            }
        }
        this.cachedTarget = null;
    },
 
    /**
     * An empty function by default, but provided so that you can perform a custom action before the dragged
     * item is dragged out of the target without dropping, and optionally cancel the onDragOut.
     * @param {Ext.dd.DragDrop} target The drop target
     * @param {Event} e The event object
     * @param {String} id The id of the dragged element
     * @return {Boolean} isValid True if the drag event is valid, else false to cancel
     * @template
     */
    beforeDragOut: function(target, e, id){
        return true;
    },
 
    /**
     * @private
     */
    onDragDrop: function(e, id){
        var target = this.cachedTarget || Ext.dd.DragDropManager.getDDById(id);
        if (this.beforeDragDrop(target, e, id) !== false) {
            if (target.isNotifyTarget) {
                if (target.notifyDrop(this, e, this.dragData) !== false) { // valid drop?
                    this.onValidDrop(target, e, id);
                } else {
                    this.onInvalidDrop(target, e, id);
                }
            } else {
                this.onValidDrop(target, e, id);
            }
 
            if (this.afterDragDrop) {
                /**
                 * An empty function by default, but provided so that you can perform a custom action
                 * after a valid drag drop has occurred by providing an implementation.
                 * @param {Ext.dd.DragDrop} target The drop target
                 * @param {Event} e The event object
                 * @param {String} id The id of the dropped element
                 * @method afterDragDrop
                 */
                this.afterDragDrop(target, e, id);
            }
        }
        delete this.cachedTarget;
    },
 
    /**
     * An empty function by default, but provided so that you can perform a custom action before the dragged
     * item is dropped onto the target and optionally cancel the onDragDrop.
     * @param {Ext.dd.DragDrop} target The drop target
     * @param {Event} e The event object
     * @param {String} id The id of the dragged element
     * @return {Boolean} isValid True if the drag drop event is valid, else false to cancel
     * @template
     */
    beforeDragDrop: function(target, e, id){
        return true;
    },
 
    /**
     * @private
     */
    onValidDrop: function(target, e, id){
        this.hideProxy();
        if(this.afterValidDrop){
            /**
             * An empty function by default, but provided so that you can perform a custom action
             * after a valid drop has occurred by providing an implementation.
             * @param {Object} target The target DD
             * @param {Event} e The event object
             * @param {String} id The id of the dropped element
             * @method afterValidDrop
             */
            this.afterValidDrop(target, e, id);
        }
    },
 
    /**
     * @private
     */
    getRepairXY: function(e, data){
        return this.el.getXY();
    },
 
    /**
     * @private
     */
    onInvalidDrop: function(target, e, id) {
        // This method may be called by the DragDropManager.
        // To preserve backwards compat, it only passes the event object
        // Here we correct the arguments.
        var me = this;
        
        if (!e) {
            e = target;
            target = null;
            id = e.getTarget().id;
        }
        if (me.beforeInvalidDrop(target, e, id) !== false) {
            if (me.cachedTarget) {
                if(me.cachedTarget.isNotifyTarget){
                    me.cachedTarget.notifyOut(me, e, me.dragData);
                }
                me.cacheTarget = null;
            }
            me.proxy.repair(me.getRepairXY(e, me.dragData), me.afterRepair, me);
 
            if (me.afterInvalidDrop) {
                /**
                * An empty function by default, but provided so that you can perform a custom action
                * after an invalid drop has occurred by providing an implementation.
                * @param {Event} e The event object
                * @param {String} id The id of the dropped element
                * @method afterInvalidDrop
                */
                me.afterInvalidDrop(e, id);
            }
        }
    },
 
    /**
     * @private
     */
    afterRepair: function() {
        var me = this;
        if (Ext.enableFx) {
            me.el.highlight(me.repairHighlightColor);
        }
        me.dragging = false;
    },
 
    /**
     * An empty function by default, but provided so that you can perform a custom action after an invalid
     * drop has occurred.
     * @param {Ext.dd.DragDrop} target The drop target
     * @param {Event} e The event object
     * @param {String} id The id of the dragged element
     * @return {Boolean} isValid True if the invalid drop should proceed, else false to cancel
     * @template
     */
    beforeInvalidDrop: function(target, e, id) {
        return true;
    },
 
    /**
     * @private
     */
    handleMouseDown: function(e) {
        if (this.dragging) {
            return;
        }
        var data = this.getDragData(e);
        if (data && this.onBeforeDrag(data, e) !== false) {
            this.dragData = data;
            this.proxy.stop();
            this.callParent(arguments);
        }
    },
 
    /**
     * An empty function by default, but provided so that you can perform a custom action before the initial
     * drag event begins and optionally cancel it.
     * @param {Object} data An object containing arbitrary data to be shared with drop targets
     * @param {Event} e The event object
     * @return {Boolean} isValid True if the drag event is valid, else false to cancel
     * @template
     */
    onBeforeDrag: function(data, e){
        return true;
    },
 
    /**
     * An empty function by default, but provided so that you can perform a custom action once the initial
     * drag event has begun.  The drag cannot be canceled from this function.
     * @param {Number} x The x position of the click on the dragged object
     * @param {Number} y The y position of the click on the dragged object
     * @method
     * @template
     */
    onStartDrag: Ext.emptyFn,
 
    alignElWithMouse: function() {
        this.proxy.ensureAttachedToBody(true);
        return this.callParent(arguments);
    },
 
    /**
     * @private
     */
    startDrag: function(x, y) {
        this.proxy.reset();
        this.proxy.hidden = false;
        this.dragging = true;
        this.proxy.update("");
        this.onInitDrag(x, y);
        this.proxy.show();
    },
 
    /**
     * @private
     */
    onInitDrag: function(x, y) {
        var clone = this.el.dom.cloneNode(true);
        clone.id = Ext.id(); // prevent duplicate ids
        this.proxy.update(clone);
        this.onStartDrag(x, y);
        return true;
    },
 
    /**
     * Returns the drag source's underlying {@link Ext.dd.StatusProxy}
     * @return {Ext.dd.StatusProxy} proxy The StatusProxy
     */
    getProxy: function() {
        return this.proxy;
    },
 
    /**
     * Hides the drag source's {@link Ext.dd.StatusProxy}
     */
    hideProxy: function() {
        this.proxy.hide();
        this.proxy.reset(true);
        this.dragging = false;
    },
 
    /**
     * @private
     */
    triggerCacheRefresh: function() {
        Ext.dd.DDM.refreshCache(this.groups);
    },
 
    /**
     * @private
     */
    b4EndDrag: function(e) {
    },
 
    /**
     * @private
     */
    endDrag : function(e){
        this.onEndDrag(this.dragData, e);
    },
 
    /**
     * @private
     */
    onEndDrag : function(data, e){
    },
 
    /**
     * @private
     */
    autoOffset : function(x, y) {
        this.setDelta(-12, -20);
    },
 
    destroy: function() {
        Ext.destroy(this.proxy);
        
        this.callParent();
    }
});