/**
 * @class Ext.grid.header.DropZone
 * @extends Ext.dd.DropZone
 * @private
 */
Ext.define('Ext.grid.header.DropZone', {
    extend: 'Ext.dd.DropZone',
    colHeaderCls: Ext.baseCSSPrefix + 'column-header',
    proxyOffsets: [-4, -9],

    constructor: function(headerCt){
        this.headerCt = headerCt;
        this.ddGroup = this.getDDGroup();
        this.callParent([headerCt.el]);
    },

    getDDGroup: function() {
        return 'header-dd-zone-' + this.headerCt.up('[scrollerOwner]').id;
    },

    getTargetFromEvent : function(e){
        return e.getTarget('.' + this.colHeaderCls);
    },

    getTopIndicator: function() {
        if (!this.topIndicator) {
            this.topIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
                cls: "col-move-top",
                html: "&#160;"
            }, true);
        }
        return this.topIndicator;
    },

    getBottomIndicator: function() {
        if (!this.bottomIndicator) {
            this.bottomIndicator = Ext.core.DomHelper.append(Ext.getBody(), {
                cls: "col-move-bottom",
                html: "&#160;"
            }, true);
        }
        return this.bottomIndicator;
    },

    getLocation: function(e, t) {
        var x      = e.getXY()[0],
            region = Ext.fly(t).getRegion(),
            pos, header;

        if ((region.right - x) <= (region.right - region.left) / 2) {
            pos = "after";
        } else {
            pos = "before";
        }
        return {
            pos: pos,
            header: Ext.getCmp(t.id),
            node: t
        };
    },

    positionIndicator: function(draggedHeader, node, e){
        var location = this.getLocation(e, node),
            header = location.header,
            pos    = location.pos,
            nextHd = draggedHeader.nextSibling('gridcolumn:not([hidden])'),
            prevHd = draggedHeader.previousSibling('gridcolumn:not([hidden])'),
            region, topIndicator, bottomIndicator, topAnchor, bottomAnchor,
            topXY, bottomXY, headerCtEl, minX, maxX;

        // Cannot drag beyond non-draggable start column
        if (!header.draggable && header.getIndex() == 0) {
            return false;
        }

        this.lastLocation = location;

        if ((draggedHeader !== header) &&
            ((pos === "before" && nextHd !== header) ||
            (pos === "after" && prevHd !== header)) &&
            !header.isDescendantOf(draggedHeader)) {

            // As we move in between different DropZones that are in the same
            // group (such as the case when in a locked grid), invalidateDrop
            // on the other dropZones.
            var allDropZones = Ext.dd.DragDropManager.getRelated(this),
                ln = allDropZones.length,
                i  = 0,
                dropZone;

            for (; i < ln; i++) {
                dropZone = allDropZones[i];
                if (dropZone !== this && dropZone.invalidateDrop) {
                    dropZone.invalidateDrop();
                }
            }


            this.valid = true;
            topIndicator = this.getTopIndicator();
            bottomIndicator = this.getBottomIndicator();
            if (pos === 'before') {
                topAnchor = 'tl';
                bottomAnchor = 'bl';
            } else {
                topAnchor = 'tr';
                bottomAnchor = 'br';
            }
            topXY = header.el.getAnchorXY(topAnchor);
            bottomXY = header.el.getAnchorXY(bottomAnchor);

            // constrain the indicators to the viewable section
            headerCtEl = this.headerCt.el;
            minX = headerCtEl.getLeft();
            maxX = headerCtEl.getRight();

            topXY[0] = Ext.Number.constrain(topXY[0], minX, maxX);
            bottomXY[0] = Ext.Number.constrain(bottomXY[0], minX, maxX);

            // adjust by offsets, this is to center the arrows so that they point
            // at the split point
            topXY[0] -= 4;
            topXY[1] -= 9;
            bottomXY[0] -= 4;

            // position and show indicators
            topIndicator.setXY(topXY);
            bottomIndicator.setXY(bottomXY);
            topIndicator.show();
            bottomIndicator.show();
        // invalidate drop operation and hide indicators
        } else {
            this.invalidateDrop();
        }
    },

    invalidateDrop: function() {
        this.valid = false;
        this.hideIndicators();
    },

    onNodeOver: function(node, dragZone, e, data) {
        if (data.header.el.dom !== node) {
            this.positionIndicator(data.header, node, e);
        }
        return this.valid ? this.dropAllowed : this.dropNotAllowed;
    },

    hideIndicators: function() {
        this.getTopIndicator().hide();
        this.getBottomIndicator().hide();
    },

    onNodeOut: function() {
        this.hideIndicators();
    },

    onNodeDrop: function(node, dragZone, e, data) {
        if (this.valid) {
            this.invalidateDrop();
            var hd = data.header,
                lastLocation = this.lastLocation,
                fromCt  = hd.ownerCt,
                fromIdx = fromCt.items.indexOf(hd), // Container.items is a MixedCollection
                toCt    = lastLocation.header.ownerCt,
                toIdx   = toCt.items.indexOf(lastLocation.header),
                headerCt = this.headerCt,
                groupCt,
                scrollerOwner;

            if (lastLocation.pos === 'after') {
                toIdx++;
            }

            // If we are dragging in between two HeaderContainers that have had the lockable
            // mixin injected we will lock/unlock headers in between sections. Note that lockable
            // does NOT currently support grouped headers.
            if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && toCt.lockedCt) {
                scrollerOwner = fromCt.up('[scrollerOwner]');
                scrollerOwner.lock(hd, toIdx);
            } else if (fromCt !== toCt && fromCt.lockableInjected && toCt.lockableInjected && fromCt.lockedCt) {
                scrollerOwner = fromCt.up('[scrollerOwner]');
                scrollerOwner.unlock(hd, toIdx);
            } else {
                // If dragging rightwards, then after removal, the insertion index will be one less when moving
                // in between the same container.
                if ((fromCt === toCt) && (toIdx > fromCt.items.indexOf(hd))) {
                    toIdx--;
                }

                // Remove dragged header from where it was without destroying it or relaying its Container
                if (fromCt !== toCt) {
                    fromCt.suspendLayout = true;
                    fromCt.remove(hd, false);
                    fromCt.suspendLayout = false;
                }

                // Dragged the last header out of the fromCt group... The fromCt group must die
                if (fromCt.isGroupHeader) {
                    if (!fromCt.items.getCount()) {
                        groupCt = fromCt.ownerCt;
                        groupCt.suspendLayout = true;
                        groupCt.remove(fromCt, false);
                        fromCt.el.dom.parentNode.removeChild(fromCt.el.dom);
                        groupCt.suspendLayout = false;
                    } else {
                        fromCt.minWidth = fromCt.getWidth() - hd.getWidth();
                        fromCt.setWidth(fromCt.minWidth);
                    }
                }

                // Move dragged header into its drop position
                toCt.suspendLayout = true;
                if (fromCt === toCt) {
                    toCt.move(fromIdx, toIdx);
                } else {
                    toCt.insert(toIdx, hd);
                }
                toCt.suspendLayout = false;

                // Group headers acquire the aggregate width of their child headers
                // Therefore a child header may not flex; it must contribute a fixed width.
                // But we restore the flex value when moving back into the main header container
                if (toCt.isGroupHeader) {
                    hd.savedFlex = hd.flex;
                    delete hd.flex;
                    hd.width = hd.getWidth();
                    // When there was previously a flex, we need to ensure we don't count for the
                    // border twice.
                    toCt.minWidth = toCt.getWidth() + hd.getWidth() - (hd.savedFlex ? 1 : 0);
                    toCt.setWidth(toCt.minWidth);
                } else {
                    if (hd.savedFlex) {
                        hd.flex = hd.savedFlex;
                        delete hd.width;
                    }
                }


                // Refresh columns cache in case we remove an emptied group column
                headerCt.purgeCache();
                headerCt.doLayout();
                headerCt.onHeaderMoved(hd, fromIdx, toIdx);
                // Emptied group header can only be destroyed after the header and grid have been refreshed
                if (!fromCt.items.getCount()) {
                    fromCt.destroy();
                }
            }
        }
    }
});
