/**
 * Private utility class for Ext.resizer.Resizer.
 * @private
 */
Ext.define('Ext.resizer.ResizeTracker', {
    extend: 'Ext.dd.DragTracker',
    dynamic: true,
    preserveRatio: false,
 
    // Resizer mousedown must blur active element
    preventDefault: false,
 
    // Default to no constraint
    constrainTo: null,
    
    proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
 
    constructor: function(config) {
        var me = this,
            widthRatio, heightRatio,
            throttledResizeFn;
 
        if (!config.el) {
            if (config.target.isComponent) {
                me.el = config.target.getEl();
            } else {
                me.el = config.target;
            }
        }
        this.callParent(arguments);
 
        // Ensure that if we are preserving aspect ratio, the largest minimum is honoured
        if (me.preserveRatio && me.minWidth && me.minHeight) {
            widthRatio = me.minWidth / me.el.getWidth();
            heightRatio = me.minHeight / me.el.getHeight();
 
            // largest ratio of minimum:size must be preserved.
            // So if a 400x200 pixel image has
            // minWidth: 50, maxWidth: 50, the maxWidth will be 400 * (50/200)... that is 100
            if (heightRatio > widthRatio) {
                me.minWidth = me.el.getWidth() * heightRatio;
            } else {
                me.minHeight = me.el.getHeight() * widthRatio;
            }
        }
 
        // If configured as throttled, create an instance version of resize which calls
        // a throttled function to perform the resize operation.
        if (me.throttle) {
            throttledResizeFn = Ext.Function.createThrottled(function() {
                    Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
                }, me.throttle);
 
            me.resize = function(box, direction, atEnd) {
                if (atEnd) {
                    Ext.resizer.ResizeTracker.prototype.resize.apply(me, arguments);
                } else {
                    throttledResizeFn.apply(null, arguments);
                }
            };
        }
    },
 
    onBeforeStart: function(e) {
        // record the startBox
        this.startBox = this.target.getBox();
    },
 
    /**
     * @private
     * Returns the object that will be resized instead of the true target on every mousemove event.
     * If dynamic is false, this will be a proxy, otherwise it will be null target.
     */
    getProxy: function() {
        var me = this;
 
        if (!me.dynamic && !me.proxy) {
            me.proxy = me.createProxy(me.target || me.el);
 
            // Only hide proxy at end if we create one dynamically
            // When a wrapped resizer is used it passes the wrapping el in as the proxy.
            me.hideProxy = true;
        }
        if (me.proxy) {
            me.proxy.show();
            return me.proxy;
        }
    },
 
    /**
     * Create a proxy for this resizer
     * @param {Ext.Component/Ext.dom.Element} target The target
     * @return {Ext.dom.Element} A proxy element
     */
    createProxy: function(target){
        var proxy,
            cls = this.proxyCls;
 
        if (target.isComponent) {
            proxy = target.getProxy().addCls(cls);
        } else {
            proxy = target.createProxy({
                tag: 'div',
                role: 'presentation',
                cls: cls,
                id: target.id + '-rzproxy'
            }, Ext.getBody());
        }
        proxy.removeCls(Ext.baseCSSPrefix + 'proxy-el');
        return proxy;
    },
 
    onStart: function(e) {
        // returns the Ext.ResizeHandle that the user started dragging
        this.activeResizeHandle = Ext.get(this.getDragTarget().id);
 
        // If we are using a proxy, ensure it is sized.
        if (!this.dynamic) {
            this.resize(this.startBox);
        }
    },
 
    onMouseDown: function (e, target) {
        // Logic to resize components on top of iframes or handle resizers bound to iframes.
        // To properly handle iframes "below" the resizable component, we cannot wait for 
        // triggerStart or onStart because if the cursor moves out of the component and over the 
        // iframe we won't detect that the drag has started. We cannot do this in
        // general because other draggable things cannot assume that mouseDown is safe
        // for this purpose. In particular ComponentDragger on a maximizable window will
        // get tricked by the maximize button onMouseDown and mask everything but will
        // never get the onMouseUp to unmask.
        this.callParent([e, target]);
        Ext.dom.Element.maskIframes();
    },
 
    onMouseUp: function(e) {
        this.callParent([e]);
        Ext.dom.Element.unmaskIframes();
    },
 
    onDrag: function(e) {
        // dynamic resizing, update dimensions during resize
        if (this.dynamic || this.proxy) {
            this.updateDimensions(e);
        }
    },
 
    updateDimensions: function(e, atEnd) {
        var me = this,
            region = me.activeResizeHandle.region,
            offset = me.getOffset(me.constrainTo ? 'dragTarget' : null),
            box = me.startBox,
            ratio,
            widthAdjust = 0,
            heightAdjust = 0,
            snappedWidth,
            snappedHeight,
            adjustX = 0,
            adjustY = 0,
            dragRatio,
            oppositeCorner,
            axis, // 1 = x, 2 = y, 3 = x and y.
            newBox,
            newHeight, newWidth;
 
        region = me.convertRegionName(region);
 
        switch (region) {
            case 'south':
                heightAdjust = offset[1];
                axis = 2;
                break;
            case 'north':
                heightAdjust = -offset[1];
                adjustY = -heightAdjust;
                axis = 2;
                break;
            case 'east':
                widthAdjust = offset[0];
                axis = 1;
                break;
            case 'west':
                widthAdjust = -offset[0];
                adjustX = -widthAdjust;
                axis = 1;
                break;
            case 'northeast':
                heightAdjust = -offset[1];
                adjustY = -heightAdjust;
                widthAdjust = offset[0];
                oppositeCorner = [box.x, box.y + box.height];
                axis = 3;
                break;
            case 'southeast':
                heightAdjust = offset[1];
                widthAdjust = offset[0];
                oppositeCorner = [box.x, box.y];
                axis = 3;
                break;
            case 'southwest':
                widthAdjust = -offset[0];
                adjustX = -widthAdjust;
                heightAdjust = offset[1];
                oppositeCorner = [box.x + box.width, box.y];
                axis = 3;
                break;
            case 'northwest':
                heightAdjust = -offset[1];
                adjustY = -heightAdjust;
                widthAdjust = -offset[0];
                adjustX = -widthAdjust;
                oppositeCorner = [box.x + box.width, box.y + box.height];
                axis = 3;
                break;
        }
 
        newBox = {
            width: box.width + widthAdjust,
            height: box.height + heightAdjust,
            x: box.x + adjustX,
            y: box.y + adjustY
        };
 
        // Snap value between stops according to configured increments
        snappedWidth = Ext.Number.snap(newBox.width, me.widthIncrement);
        snappedHeight = Ext.Number.snap(newBox.height, me.heightIncrement);
        if (snappedWidth !== newBox.width || snappedHeight !== newBox.height){
            switch (region) {
                case 'northeast':
                    newBox.y -= snappedHeight - newBox.height;
                    break;
                case 'north':
                    newBox.y -= snappedHeight - newBox.height;
                    break;
                case 'southwest':
                    newBox.x -= snappedWidth - newBox.width;
                    break;
                case 'west':
                    newBox.x -= snappedWidth - newBox.width;
                    break;
                case 'northwest':
                    newBox.x -= snappedWidth - newBox.width;
                    newBox.y -= snappedHeight - newBox.height;
            }
            newBox.width = snappedWidth;
            newBox.height = snappedHeight;
        }
 
        // out of bounds
        if (newBox.width < me.minWidth || newBox.width > me.maxWidth) {
            newBox.width = Ext.Number.constrain(newBox.width, me.minWidth, me.maxWidth);
 
            // Re-adjust the X position if we were dragging the west side
            if (adjustX) {
                newBox.x = box.x + (box.width - newBox.width);
            }
        } else {
            me.lastX = newBox.x;
        }
        if (newBox.height < me.minHeight || newBox.height > me.maxHeight) {
            newBox.height = Ext.Number.constrain(newBox.height, me.minHeight, me.maxHeight);
 
            // Re-adjust the Y position if we were dragging the north side
            if (adjustY) {
                newBox.y = box.y + (box.height - newBox.height);
            }
        } else {
            me.lastY = newBox.y;
        }
 
        // If this is configured to preserve the aspect ratio, or they are dragging using the shift key
        if (me.preserveRatio || e.shiftKey) {
            ratio = me.startBox.width / me.startBox.height;
 
            // Calculate aspect ratio constrained values.
            newHeight = Math.min(Math.max(me.minHeight, newBox.width / ratio), me.maxHeight);
            newWidth = Math.min(Math.max(me.minWidth, newBox.height * ratio), me.maxWidth);
 
            // X axis: width-only change, height must obey
            if (axis === 1) {
                newBox.height = newHeight;
            }
 
            // Y axis: height-only change, width must obey
            else if (axis === 2) {
                newBox.width = newWidth;
            }
 
            // Corner drag.
            else {
                // Drag ratio is the ratio of the mouse point from the opposite corner.
                // Basically what edge we are dragging, a horizontal edge or a vertical edge.
                dragRatio = Math.abs(oppositeCorner[0] - this.lastXY[0]) / Math.abs(oppositeCorner[1] - this.lastXY[1]);
 
                // If drag ratio > aspect ratio then width is dominant and height must obey
                if (dragRatio > ratio) {
                    newBox.height = newHeight;
                } else {
                    newBox.width = newWidth;
                }
 
                // Handle dragging start coordinates
                if (region === 'northeast') {
                    newBox.y = box.y - (newBox.height - box.height);
                } else if (region === 'northwest') {
                    newBox.y = box.y - (newBox.height - box.height);
                    newBox.x = box.x - (newBox.width - box.width);
                } else if (region === 'southwest') {
                    newBox.x = box.x - (newBox.width - box.width);
                }
            }
        }
 
        // Keep track of whether position needs changing
        me.setPosition = newBox.x !== me.startBox.x || newBox.y !== me.startBox.y;
        me.resize(newBox, atEnd);
    },
 
    resize: function(box, atEnd) {
        var me = this,
            target,
            setPosition = me.setPosition;
 
        // We are live resizing the target, or at the end: Size the target
        if (me.dynamic || (!me.dynamic && atEnd)) {
            // Resize the target
            if (setPosition) {
                me.target.setBox(box);
            } else {
                me.target.setSize(box.width, box.height);
            }
 
        }
 
        // In the middle of a resize - just resize the proxy
        if (!atEnd) {
            target = me.getProxy();
            if (target && target !== me.target) {
                if (setPosition || me.hideProxy) {
                    target.setBox(box);
                } else {
                    target.setSize(box.width, box.height);
                }
            }
        }
    },
 
    onEnd: function(e) {
        this.updateDimensions(e, true);
        if (this.proxy && this.hideProxy) {
            this.proxy.hide();
        }
    },
 
    convertRegionName: function(name) {
        return name;
    }
});