    /**
     * @class Ext.draw.Sprite
     * @extends Object
     *
     * A Sprite is an object rendered in a Drawing surface. There are different options and types of sprites.
     * The configuration of a Sprite is an object with the following properties:
     *
     * - **type** - (String) The type of the sprite. Possible options are 'circle', 'path', 'rect', 'text', 'square'.
     * - **width** - (Number) Used in rectangle sprites, the width of the rectangle.
     * - **height** - (Number) Used in rectangle sprites, the height of the rectangle.
     * - **size** - (Number) Used in square sprites, the dimension of the square.
     * - **radius** - (Number) Used in circle sprites, the radius of the circle.
     * - **x** - (Number) The position along the x-axis.
     * - **y** - (Number) The position along the y-axis.
     * - **path** - (Array) Used in path sprites, the path of the sprite written in SVG-like path syntax.
     * - **opacity** - (Number) The opacity of the sprite.
     * - **fill** - (String) The fill color.
     * - **stroke** - (String) The stroke color.
     * - **stroke-width** - (Number) The width of the stroke.
     * - **font** - (String) Used with text type sprites. The full font description. Uses the same syntax as the CSS `font` parameter.
     * - **text** - (String) Used with text type sprites. The text itself.
     *
     * Additionally there are three transform objects that can be set with `setAttributes` which are `translate`, `rotate` and
     * `scale`.
     *
     * For translate, the configuration object contains x and y attributes that indicate where to
     * translate the object. For example:
     *
     *     sprite.setAttributes({
     *       translate: {
     *        x: 10,
     *        y: 10
     *       }
     *     }, true);
     *
     * For rotation, the configuration object contains x and y attributes for the center of the rotation (which are optional),
     * and a `degrees` attribute that specifies the rotation in degrees. For example:
     *
     *     sprite.setAttributes({
     *       rotate: {
     *        degrees: 90
     *       }
     *     }, true);
     *
     * For scaling, the configuration object contains x and y attributes for the x-axis and y-axis scaling. For example:
     *
     *     sprite.setAttributes({
     *       scale: {
     *        x: 10,
     *        y: 3
     *       }
     *     }, true);
     *
     * Sprites can be created with a reference to a {@link Ext.draw.Surface}
     *
     *      var drawComponent = Ext.create('Ext.draw.Component', options here...);
     *
     *      var sprite = Ext.create('Ext.draw.Sprite', {
     *          type: 'circle',
     *          fill: '#ff0',
     *          surface: drawComponent.surface,
     *          radius: 5
     *      });
     *
     * Sprites can also be added to the surface as a configuration object:
     *
     *      var sprite = drawComponent.surface.add({
     *          type: 'circle',
     *          fill: '#ff0',
     *          radius: 5
     *      });
     *
     * In order to properly apply properties and render the sprite we have to
     * `show` the sprite setting the option `redraw` to `true`:
     *
     *      sprite.show(true);
     *
     * The constructor configuration object of the Sprite can also be used and passed into the {@link Ext.draw.Surface}
     * add method to append a new sprite to the canvas. For example:
     *
     *     drawComponent.surface.add({
     *         type: 'circle',
     *         fill: '#ffc',
     *         radius: 100,
     *         x: 100,
     *         y: 100
     *     });
     */
    Ext.define('Ext.draw.Sprite', {
        /* Begin Definitions */

        mixins: {
            observable: 'Ext.util.Observable',
            animate: 'Ext.util.Animate'
        },

        requires: ['Ext.draw.SpriteDD'],

        /* End Definitions */

        dirty: false,
        dirtyHidden: false,
        dirtyTransform: false,
        dirtyPath: true,
        dirtyFont: true,
        zIndexDirty: true,
        isSprite: true,
        zIndex: 0,
        fontProperties: [
            'font',
            'font-size',
            'font-weight',
            'font-style',
            'font-family',
            'text-anchor',
            'text'
        ],
        pathProperties: [
            'x',
            'y',
            'd',
            'path',
            'height',
            'width',
            'radius',
            'r',
            'rx',
            'ry',
            'cx',
            'cy'
        ],
        constructor: function(config) {
            var me = this;
            config = config || {};
            me.id = Ext.id(null, 'ext-sprite-');
            me.transformations = [];
            Ext.copyTo(this, config, 'surface,group,type,draggable');
            //attribute bucket
            me.bbox = {};
            me.attr = {
                zIndex: 0,
                translation: {
                    x: null,
                    y: null
                },
                rotation: {
                    degrees: null,
                    x: null,
                    y: null
                },
                scaling: {
                    x: null,
                    y: null,
                    cx: null,
                    cy: null
                }
            };
            //delete not bucket attributes
            delete config.surface;
            delete config.group;
            delete config.type;
            delete config.draggable;
            me.setAttributes(config);
            me.addEvents(
                'beforedestroy',
                'destroy',
                'render',
                'mousedown',
                'mouseup',
                'mouseover',
                'mouseout',
                'mousemove',
                'click'
            );
            me.mixins.observable.constructor.apply(this, arguments);
        },

        /**
         * <p>If this Sprite is configured {@link #draggable}, this property will contain
         * an instance of {@link Ext.dd.DragSource} which handles dragging the Sprite.</p>
         * The developer must provide implementations of the abstract methods of {@link Ext.dd.DragSource}
         * in order to supply behaviour for each stage of the drag/drop process. See {@link #draggable}.
         * @type Ext.dd.DragSource.
         * @property dd
         */
        initDraggable: function() {
            var me = this;
            me.draggable = true;
            //create element if it doesn't exist.
            if (!me.el) {
                me.surface.createSprite(me);
            }
            me.dd = Ext.create('Ext.draw.SpriteDD', me, Ext.isBoolean(me.draggable) ? null : me.draggable);
            me.on('beforedestroy', me.dd.destroy, me.dd);
        },

        /**
         * Change the attributes of the sprite.
         * @param {Object} attrs attributes to be changed on the sprite.
         * @param {Boolean} redraw Flag to immediatly draw the change.
         * @return {Ext.draw.Sprite} this
         */
        setAttributes: function(attrs, redraw) {
            var me = this,
                fontProps = me.fontProperties,
                fontPropsLength = fontProps.length,
                pathProps = me.pathProperties,
                pathPropsLength = pathProps.length,
                hasSurface = !!me.surface,
                custom = hasSurface && me.surface.customAttributes || {},
                spriteAttrs = me.attr,
                attr, i, translate, translation, rotate, rotation, scale, scaling;

            for (attr in custom) {
                if (attrs.hasOwnProperty(attr) && typeof custom[attr] == "function") {
                    Ext.apply(attrs, custom[attr].apply(me, [].concat(attrs[attr])));
                }
            }

            // Flag a change in hidden
            if (!!attrs.hidden !== !!spriteAttrs.hidden) {
                me.dirtyHidden = true;
            }

            // Flag path change
            for (i = 0; i < pathPropsLength; i++) {
                attr = pathProps[i];
                if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
                    me.dirtyPath = true;
                    break;
                }
            }

            // Flag zIndex change
            if ('zIndex' in attrs) {
                me.zIndexDirty = true;
            }

            // Flag font/text change
            for (i = 0; i < fontPropsLength; i++) {
                attr = fontProps[i];
                if (attr in attrs && attrs[attr] !== spriteAttrs[attr]) {
                    me.dirtyFont = true;
                    break;
                }
            }

            translate = attrs.translate;
            translation = spriteAttrs.translation;
            if (translate) {
                if ((translate.x && translate.x !== translation.x) ||
                    (translate.y && translate.y !== translation.y)) {
                    Ext.apply(translation, translate);
                    me.dirtyTransform = true;
                }
                delete attrs.translate;
            }

            rotate = attrs.rotate;
            rotation = spriteAttrs.rotation;
            if (rotate) {
                if ((rotate.x && rotate.x !== rotation.x) ||
                    (rotate.y && rotate.y !== rotation.y) ||
                    (rotate.degrees && rotate.degrees !== rotation.degrees)) {
                    Ext.apply(rotation, rotate);
                    me.dirtyTransform = true;
                }
                delete attrs.rotate;
            }

            scale = attrs.scale;
            scaling = spriteAttrs.scaling;
            if (scale) {
                if ((scale.x && scale.x !== scaling.x) ||
                    (scale.y && scale.y !== scaling.y) ||
                    (scale.cx && scale.cx !== scaling.cx) ||
                    (scale.cy && scale.cy !== scaling.cy)) {
                    Ext.apply(scaling, scale);
                    me.dirtyTransform = true;
                }
                delete attrs.scale;
            }

            Ext.apply(spriteAttrs, attrs);
            me.dirty = true;

            if (redraw === true && hasSurface) {
                me.redraw();
            }
            return this;
        },

        /**
         * Retrieve the bounding box of the sprite. This will be returned as an object with x, y, width, and height properties.
         * @return {Object} bbox
         */
        getBBox: function() {
            return this.surface.getBBox(this);
        },

        setText: function(text) {
            return this.surface.setText(this, text);
        },

        /**
         * Hide the sprite.
         * @param {Boolean} redraw Flag to immediatly draw the change.
         * @return {Ext.draw.Sprite} this
         */
        hide: function(redraw) {
            this.setAttributes({
                hidden: true
            }, redraw);
            return this;
        },

        /**
         * Show the sprite.
         * @param {Boolean} redraw Flag to immediatly draw the change.
         * @return {Ext.draw.Sprite} this
         */
        show: function(redraw) {
            this.setAttributes({
                hidden: false
            }, redraw);
            return this;
        },

        /**
         * Remove the sprite.
         */
        remove: function() {
            if (this.surface) {
                this.surface.remove(this);
                return true;
            }
            return false;
        },

        onRemove: function() {
            this.surface.onRemove(this);
        },

        /**
         * Removes the sprite and clears all listeners.
         */
        destroy: function() {
            var me = this;
            if (me.fireEvent('beforedestroy', me) !== false) {
                me.remove();
                me.surface.onDestroy(me);
                me.clearListeners();
                me.fireEvent('destroy');
            }
        },

        /**
         * Redraw the sprite.
         * @return {Ext.draw.Sprite} this
         */
        redraw: function() {
            this.surface.renderItem(this);
            return this;
        },

        /**
         * Wrapper for setting style properties, also takes single object parameter of multiple styles.
         * @param {String/Object} property The style property to be set, or an object of multiple styles.
         * @param {String} value (optional) The value to apply to the given property, or null if an object was passed.
         * @return {Ext.draw.Sprite} this
         */
        setStyle: function() {
            this.el.setStyle.apply(this.el, arguments);
            return this;
        },

        /**
         * Adds one or more CSS classes to the element. Duplicate classes are automatically filtered out.  Note this method
         * is severly limited in VML.
         * @param {String/Array} className The CSS class to add, or an array of classes
         * @return {Ext.draw.Sprite} this
         */
        addCls: function(obj) {
            this.surface.addCls(this, obj);
            return this;
        },

        /**
         * Removes one or more CSS classes from the element.
         * @param {String/Array} className The CSS class to remove, or an array of classes.  Note this method
         * is severly limited in VML.
         * @return {Ext.draw.Sprite} this
         */
        removeCls: function(obj) {
            this.surface.removeCls(this, obj);
            return this;
        }
    });