/**
 * This plugin can enable a cell to cell drag and drop operation within the same grid view.
 *
 * Note that the plugin must be added to the grid view, not to the grid panel. For example,
 * using {@link Ext.panel.Table viewConfig}:
 *
 *      viewConfig: {
 *          plugins: {
 *              celldragdrop: {
 *                  // Remove text from source cell and replace with value of emptyText.
 *                  applyEmptyText: true,
 *
 *                  //emptyText: Ext.String.htmlEncode('<<foo>>'),
 *
 *                  // Will only allow drops of the same type.
 *                  enforceType: true
 *              }
 *          }
 *      }
 */
Ext.define('Ext.ux.CellDragDrop', {
    extend: 'Ext.plugin.Abstract',
    alias: 'plugin.celldragdrop',
 
    uses: ['Ext.view.DragZone'],
 
    /**
     * @cfg {Boolean} enforceType
     * Set to `true` to only allow drops of the same type.
     *
     * Defaults to `false`.
     */
    enforceType: false,
 
    /**
     * @cfg {Boolean} applyEmptyText
     * If `true`, then use the value of {@link #emptyText} to replace the drag record's value
     * after a node drop. Note that, if dropped on a cell of a different type, it will convert
     * the default text according to its own conversion rules.
     *
     * Defaults to `false`.
     */
    applyEmptyText: false,
 
    /**
     * @cfg {String} emptyText
     * If {@link #applyEmptyText} is `true`, then this value as the drag record's value after
     * a node drop.
     *
     * Defaults to an empty string.
     */
    emptyText: '',
 
    /**
     * @cfg {String} dropBackgroundColor
     * The default background color for when a drop is allowed.
     *
     * Defaults to green.
     */
    dropBackgroundColor: 'green',
 
    /**
     * @cfg {String} noDropBackgroundColor
     * The default background color for when a drop is not allowed.
     *
     * Defaults to red.
     */
    noDropBackgroundColor: 'red',
 
    /**
     * @cfg {String} dragText
     * The text to show while dragging.
     *
     * Two placeholders can be used in the text:
     *
     * - `{0}` The number of selected items.
     * - `{1}` 's' when more than 1 items (only useful for English).
     * @locale
     */
    dragText: '{0} selected row{1}',
 
    /**
     * @cfg {String} ddGroup
     * A named drag drop group to which this object belongs. If a group is specified, then both
     * the DragZones and DropZone used by this plugin will only interact with other drag drop
     * objects in the same group.
     */
    ddGroup: "GridDD",
 
    /**
     * @cfg {Boolean} enableDrop
     * Set to `false` to disallow the View from accepting drop gestures.
     */
    enableDrop: true,
 
    /**
     * @cfg {Boolean} enableDrag
     * Set to `false` to disallow dragging items from the View.
     */
    enableDrag: true,
 
    /**
     * @cfg {Object/Boolean} containerScroll
     * True to register this container with the Scrollmanager for auto scrolling during drag
     * operations. A {@link Ext.dd.ScrollManager} configuration may also be passed.
     */
    containerScroll: false,
 
    init: function(view) {
        var me = this;
 
        view.on('render', me.onViewRender, me, {
            single: true
        });
    },
 
    destroy: function() {
        var me = this;
 
        me.dragZone = me.dropZone = Ext.destroy(me.dragZone, me.dropZone);
 
        me.callParent();
    },
 
    enable: function() {
        var me = this;
 
        if (me.dragZone) {
            me.dragZone.unlock();
        }
 
        if (me.dropZone) {
            me.dropZone.unlock();
        }
 
        me.callParent();
    },
 
    disable: function() {
        var me = this;
 
        if (me.dragZone) {
            me.dragZone.lock();
        }
 
        if (me.dropZone) {
            me.dropZone.lock();
        }
 
        me.callParent();
    },
 
    onViewRender: function(view) {
        var me = this,
            scrollEl;
 
        if (me.enableDrag) {
            if (me.containerScroll) {
                scrollEl = view.getEl();
            }
 
            me.dragZone = new Ext.view.DragZone({
                view: view,
                ddGroup: me.dragGroup || me.ddGroup,
                dragText: me.dragText,
                containerScroll: me.containerScroll,
                scrollEl: scrollEl,
                getDragData: function(e) {
                    var view = this.view,
                        item = e.getTarget(view.getItemSelector()),
                        record = view.getRecord(item),
                        cell = e.getTarget(view.getCellSelector()),
                        dragEl, header;
 
                    if (item) {
                        dragEl = document.createElement('div');
                        dragEl.className = 'x-form-text';
                        dragEl.appendChild(
                            document.createTextNode(cell.textContent || cell.innerText)
                        );
 
                        header = view.getHeaderByCell(cell);
 
                        return {
                            event: new Ext.EventObjectImpl(e),
                            ddel: dragEl,
                            item: e.target,
                            columnName: header.dataIndex,
                            record: record
                        };
                    }
                },
 
                onInitDrag: function(x, y) {
                    var self = this,
                        data = self.dragData,
                        view = self.view,
                        selectionModel = view.getSelectionModel(),
                        record = data.record,
                        el = data.ddel;
 
                    // Update the selection to match what would have been selected if the user had
                    // done a full click on the target node rather than starting a drag from it.
                    if (!selectionModel.isSelected(record)) {
                        selectionModel.select(record, true);
                    }
 
                    Ext.fly(self.ddel).update(el.textContent || el.innerText);
                    self.proxy.update(self.ddel);
                    self.onStartDrag(x, y);
 
                    return true;
                }
            });
        }
 
        if (me.enableDrop) {
            me.dropZone = new Ext.dd.DropZone(view.el, {
                view: view,
                ddGroup: me.dropGroup || me.ddGroup,
                containerScroll: true,
 
                getTargetFromEvent: function(e) {
                    var self = this,
                        view = self.view,
                        cell = e.getTarget(view.cellSelector),
                        row, header;
 
                    // Ascertain whether the mousemove is within a grid cell.
                    if (cell) {
                        row = view.findItemByChild(cell);
                        header = view.getHeaderByCell(cell);
 
                        if (row && header) {
                            return {
                                node: cell,
                                record: view.getRecord(row),
                                columnName: header.dataIndex
                            };
                        }
                    }
                },
 
                // On Node enter, see if it is valid for us to drop the field on that type of column
                onNodeEnter: function(target, dd, e, dragData) {
                    var self = this,
                        destType, sourceType;
 
                    destType = target.record.getField(target.columnName).type.toUpperCase();
                    sourceType = dragData.record.getField(dragData.columnName).type.toUpperCase();
 
                    delete self.dropOK;
 
                    // Return if no target node or if over the same cell as the source of the drag.
                    if (!target || target.node === dragData.item.parentNode) {
                        return;
                    }
 
                    // Check whether the data type of the column being dropped on accepts the
                    // dragged field type. If so, set dropOK flag, and highlight the target node.
                    if (me.enforceType && destType !== sourceType) {
                        self.dropOK = false;
 
                        if (me.noDropCls) {
                            Ext.fly(target.node).addCls(me.noDropCls);
                        }
                        else {
                            Ext.fly(target.node).applyStyles({
                                backgroundColor: me.noDropBackgroundColor
                            });
                        }
 
                        return false;
                    }
 
                    self.dropOK = true;
 
                    if (me.dropCls) {
                        Ext.fly(target.node).addCls(me.dropCls);
                    }
                    else {
                        Ext.fly(target.node).applyStyles({
                            backgroundColor: me.dropBackgroundColor
                        });
                    }
                },
 
                // Return the class name to add to the drag proxy. This provides a visual indication
                // of drop allowed or not allowed.
                onNodeOver: function(target, dd, e, dragData) {
                    return this.dropOK ? this.dropAllowed : this.dropNotAllowed;
                },
 
                // Highlight the target node.
                onNodeOut: function(target, dd, e, dragData) {
                    var cls = this.dropOK ? me.dropCls : me.noDropCls;
 
                    if (cls) {
                        Ext.fly(target.node).removeCls(cls);
                    }
                    else {
                        Ext.fly(target.node).applyStyles({
                            backgroundColor: ''
                        });
                    }
                },
 
                // Process the drop event if we have previously ascertained that a drop is OK.
                onNodeDrop: function(target, dd, e, dragData) {
                    if (this.dropOK) {
                        target.record.set(
                            target.columnName, dragData.record.get(dragData.columnName)
                        );
 
                        if (me.applyEmptyText) {
                            dragData.record.set(dragData.columnName, me.emptyText);
                        }
 
                        return true;
                    }
                },
 
                onCellDrop: Ext.emptyFn
            });
        }
    }
});