/**
 * @class Ext.resizer.Resizer
 * <p>Applies drag handles to an element or component to make it resizable. The
 * drag handles are inserted into the element (or component's element) and
 * positioned absolute.</p>
 *
 * <p>Textarea and img elements will be wrapped with an additional div because
 * these elements do not support child nodes. The original element can be accessed
 * through the originalTarget property.</p>
 *
 * <p>Here is the list of valid resize handles:</p>
 * <pre>
Value   Description
------  -------------------
 'n'     north
 's'     south
 'e'     east
 'w'     west
 'nw'    northwest
 'sw'    southwest
 'se'    southeast
 'ne'    northeast
 'all'   all
</pre>
 * {@img Ext.resizer.Resizer/Ext.resizer.Resizer.png Ext.resizer.Resizer component}
 * <p>Here's an example showing the creation of a typical Resizer:</p>
 * <pre><code>
    <div id="elToResize" style="width:200px; height:100px; background-color:#000000;"></div>

    Ext.create('Ext.resizer.Resizer', {
        el: 'elToResize',
        handles: 'all',
        minWidth: 200,
        minHeight: 100,
        maxWidth: 500,
        maxHeight: 400,
        pinned: true
    });
</code></pre>
*/
Ext.define('Ext.resizer.Resizer', {
    mixins: {
        observable: 'Ext.util.Observable'
    },
    uses: ['Ext.resizer.ResizeTracker', 'Ext.Component'],

    alternateClassName: 'Ext.Resizable',

    handleCls: Ext.baseCSSPrefix + 'resizable-handle',
    pinnedCls: Ext.baseCSSPrefix + 'resizable-pinned',
    overCls:   Ext.baseCSSPrefix + 'resizable-over',
    proxyCls:  Ext.baseCSSPrefix + 'resizable-proxy',
    wrapCls:   Ext.baseCSSPrefix + 'resizable-wrap',

    /**
     * @cfg {Boolean} dynamic
     * <p>Specify as true to update the {@link #target} (Element or {@link Ext.Component Component}) dynamically during dragging.
     * This is <code>true</code> by default, but the {@link Ext.Component Component} class passes <code>false</code> when it
     * is configured as {@link Ext.Component#resizable}.</p>
     * <p>If specified as <code>false</code>, a proxy element is displayed during the resize operation, and the {@link #target}
     * is updated on mouseup.</p>
     */
    dynamic: true,

    /**
     * @cfg {String} handles String consisting of the resize handles to display. Defaults to 's e se' for
     * Elements and fixed position Components. Defaults to 8 point resizing for floating Components (such as Windows).
     * Specify either <code>'all'</code> or any of <code>'n s e w ne nw se sw'</code>.
     */
    handles: 's e se',

    /**
     * @cfg {Number} height Optional. The height to set target to in pixels (defaults to null)
     */
    height : null,

    /**
     * @cfg {Number} width Optional. The width to set the target to in pixels (defaults to null)
     */
    width : null,

    /**
     * @cfg {Number} minHeight The minimum height for the element (defaults to 20)
     */
    minHeight : 20,

    /**
     * @cfg {Number} minWidth The minimum width for the element (defaults to 20)
     */
    minWidth : 20,

    /**
     * @cfg {Number} maxHeight The maximum height for the element (defaults to 10000)
     */
    maxHeight : 10000,

    /**
     * @cfg {Number} maxWidth The maximum width for the element (defaults to 10000)
     */
    maxWidth : 10000,

    /**
     * @cfg {Boolean} pinned True to ensure that the resize handles are always
     * visible, false indicates resizing by cursor changes only (defaults to false)
     */
    pinned: false,

    /**
     * @cfg {Boolean} preserveRatio True to preserve the original ratio between height
     * and width during resize (defaults to false)
     */
    preserveRatio: false,

    /**
     * @cfg {Boolean} transparent True for transparent handles. This is only applied at config time. (defaults to false)
     */
    transparent: false,

    /**
     * @cfg {Mixed} constrainTo Optional. An element, or a {@link Ext.util.Region} into which the resize operation
     * must be constrained.
     */

    possiblePositions: {
        n:  'north',
        s:  'south',
        e:  'east',
        w:  'west',
        se: 'southeast',
        sw: 'southwest',
        nw: 'northwest',
        ne: 'northeast'
    },

    /**
     * @cfg {Mixed} target The Element or Component to resize.
     */

    /**
     * Outer element for resizing behavior.
     * @type Ext.core.Element
     * @property el
     */

    constructor: function(config) {
        var me = this,
            target,
            tag,
            handles = me.handles,
            handleCls,
            possibles,
            len,
            i = 0,
            pos;

        this.addEvents(
            /**
             * @event beforeresize
             * Fired before resize is allowed. Return false to cancel resize.
             * @param {Ext.resizer.Resizer} this
             * @param {Number} width The start width
             * @param {Number} height The start height
             * @param {Ext.EventObject} e The mousedown event
             */
            'beforeresize',
            /**
             * @event resizedrag
             * Fires during resizing. Return false to cancel resize.
             * @param {Ext.resizer.Resizer} this
             * @param {Number} width The new width
             * @param {Number} height The new height
             * @param {Ext.EventObject} e The mousedown event
             */
            'resizedrag',
            /**
             * @event resize
             * Fired after a resize.
             * @param {Ext.resizer.Resizer} this
             * @param {Number} width The new width
             * @param {Number} height The new height
             * @param {Ext.EventObject} e The mouseup event
             */
            'resize'
        );

        if (Ext.isString(config) || Ext.isElement(config) || config.dom) {
            target = config;
            config = arguments[1] || {};
            config.target = target;
        }
        // will apply config to this
        me.mixins.observable.constructor.call(me, config);

        // If target is a Component, ensure that we pull the element out.
        // Resizer must examine the underlying Element.
        target = me.target;
        if (target) {
            if (target.isComponent) {
                me.el = target.getEl();
                if (target.minWidth) {
                    me.minWidth = target.minWidth;
                }
                if (target.minHeight) {
                    me.minHeight = target.minHeight;
                }
                if (target.maxWidth) {
                    me.maxWidth = target.maxWidth;
                }
                if (target.maxHeight) {
                    me.maxHeight = target.maxHeight;
                }
                if (target.floating) {
                    if (!this.hasOwnProperty('handles')) {
                        this.handles = 'n ne e se s sw w nw';
                    }
                }
            } else {
                me.el = me.target = Ext.get(target);
            }
        }
        // Backwards compatibility with Ext3.x's Resizable which used el as a config.
        else {
            me.target = me.el = Ext.get(me.el);
        }

        // Tags like textarea and img cannot
        // have children and therefore must
        // be wrapped
        tag = me.el.dom.tagName;
        if (tag == 'TEXTAREA' || tag == 'IMG') {
            /**
             * Reference to the original resize target if the element of the original
             * resize target was an IMG or a TEXTAREA which must be wrapped in a DIV.
             * @type Mixed
             * @property originalTarget
             */
            me.originalTarget = me.target;
            me.target = me.el = me.el.wrap({
                cls: me.wrapCls,
                id: me.el.id + '-rzwrap'
            });

            // Transfer originalTarget's positioning/sizing
            me.el.setPositioning(me.originalTarget.getPositioning());
            me.originalTarget.clearPositioning();
            var box = me.originalTarget.getBox();
            me.el.setBox(box);
        }

        // Position the element, this enables us to absolute position
        // the handles within this.el
        me.el.position();
        if (me.pinned) {
            me.el.addCls(me.pinnedCls);
        }

        /**
         * @type Ext.resizer.ResizeTracker
         * @property resizeTracker
         */
        me.resizeTracker = Ext.create('Ext.resizer.ResizeTracker', {
            disabled: me.disabled,
            target: me.target,
            constrainTo: me.constrainTo,
            overCls: me.overCls,
            throttle: me.throttle,
            originalTarget: me.originalTarget,
            delegate: '.' + me.handleCls,
            dynamic: me.dynamic,
            preserveRatio: me.preserveRatio,
            minHeight: me.minHeight,
            maxHeight: me.maxHeight,
            minWidth: me.minWidth,
            maxWidth: me.maxWidth
        });

        // Relay the ResizeTracker's superclass events as our own resize events
        me.resizeTracker.on('mousedown', me.onBeforeResize, me);
        me.resizeTracker.on('drag', me.onResize, me);
        me.resizeTracker.on('dragend', me.onResizeEnd, me);

        if (me.handles == 'all') {
            me.handles = 'n s e w ne nw se sw';
        }

        handles = me.handles = me.handles.split(/ |\s*?[,;]\s*?/);
        possibles = me.possiblePositions;
        len = handles.length;
        handleCls = me.handleCls + ' ' + (this.target.isComponent ? (me.target.baseCls + '-handle ') : '') + me.handleCls + '-';

        for(; i < len; i++){
            // if specified and possible, create
            if (handles[i] && possibles[handles[i]]) {
                pos = possibles[handles[i]];
                // store a reference in this.east, this.west, etc

                me[pos] = Ext.create('Ext.Component', {
                    owner: this,
                    region: pos,
                    cls: handleCls + pos,
                    renderTo: me.el
                });
                me[pos].el.unselectable();
                if (me.transparent) {
                    me[pos].el.setOpacity(0);
                }
            }
        }

        // Constrain within configured maxima
        if (Ext.isNumber(me.width)) {
            me.width = Ext.Number.constrain(me.width, me.minWidth, me.maxWidth);
        }
        if (Ext.isNumber(me.height)) {
            me.height = Ext.Number.constrain(me.height, me.minHeight, me.maxHeight);
        }

        // Size the element
        if (me.width != null || me.height != null) {
            if (me.originalTarget) {
                me.originalTarget.setWidth(me.width);
                me.originalTarget.setHeight(me.height);
            }
            me.resizeTo(me.width, me.height);
        }

        me.forceHandlesHeight();
    },

    disable: function() {
        this.resizeTracker.disable();
    },

    enable: function() {
        this.resizeTracker.enable();
    },

    /**
     * @private Relay the Tracker's mousedown event as beforeresize
     * @param tracker The Resizer
     * @param e The Event
     */
    onBeforeResize: function(tracker, e) {
        var b = this.target.getBox();
        return this.fireEvent('beforeresize', this, b.width, b.height, e);
    },

    /**
     * @private Relay the Tracker's drag event as resizedrag
     * @param tracker The Resizer
     * @param e The Event
     */
    onResize: function(tracker, e) {
        var me = this,
            b = me.target.getBox();
        me.forceHandlesHeight();
        return me.fireEvent('resizedrag', me, b.width, b.height, e);
    },

    /**
     * @private Relay the Tracker's dragend event as resize
     * @param tracker The Resizer
     * @param e The Event
     */
    onResizeEnd: function(tracker, e) {
        var me = this,
            b = me.target.getBox();
        me.forceHandlesHeight();
        return me.fireEvent('resize', me, b.width, b.height, e);
    },

    /**
     * Perform a manual resize and fires the 'resize' event.
     * @param {Number} width
     * @param {Number} height
     */
    resizeTo : function(width, height){
        this.target.setSize(width, height);
        this.fireEvent('resize', this, width, height, null);
    },

    /**
     * <p>Returns the element that was configured with the el or target config property.
     * If a component was configured with the target property then this will return the
     * element of this component.<p>
     * <p>Textarea and img elements will be wrapped with an additional div because
      * these elements do not support child nodes. The original element can be accessed
     * through the originalTarget property.</p>
     * @return {Element} element
     */
    getEl : function() {
        return this.el;
    },

    /**
     * <p>Returns the element or component that was configured with the target config property.<p>
     * <p>Textarea and img elements will be wrapped with an additional div because
      * these elements do not support child nodes. The original element can be accessed
     * through the originalTarget property.</p>
     * @return {Element/Component}
     */
    getTarget: function() {
        return this.target;
    },

    destroy: function() {
        var h;
        for (var i = 0, l = this.handles.length; i < l; i++) {
            h = this[this.possiblePositions[this.handles[i]]];
            delete h.owner;
            Ext.destroy(h);
        }
    },

    /**
     * @private
     * Fix IE6 handle height issue.
     */
    forceHandlesHeight : function() {
        var me = this,
            handle;
        if (Ext.isIE6) {
            handle = me.east; 
            if (handle) {
                handle.setHeight(me.el.getHeight());
            }
            handle = me.west; 
            if (handle) {
                handle.setHeight(me.el.getHeight());
            }
            me.el.repaint();
        }
    }
});
