/**
 * @class Ext.draw.sprite.Composite
 * @extends Ext.draw.sprite.Sprite
 * 
 * Represents a group of sprites.
 */
Ext.define('Ext.draw.sprite.Composite', {
    extend: 'Ext.draw.sprite.Sprite',
    alias: 'sprite.composite',
    type: 'composite',
    isComposite: true,
 
    config: {
        sprites: []
    },
 
    constructor: function (config) {
        this.sprites = [];
        this.map = {};
        this.callParent([config]);
    },
 
    /**
     * Adds a sprite to the composite.
     * @param {Ext.draw.sprite.Sprite|Object} sprite 
     */
    add: function (sprite) {
        if (!sprite) {
            return null;
        }
        if (!sprite.isSprite) {
            sprite = Ext.create('sprite.' + sprite.type, sprite);
        }
        sprite.setParent(this);
        sprite.setSurface(this.getSurface());
 
        var me = this,
            attr = me.attr,
            oldTransformations = sprite.applyTransformations;
 
        sprite.applyTransformations = function (force) {
            if (sprite.attr.dirtyTransform) {
                attr.dirtyTransform = true;
                attr.bbox.plain.dirty = true;
                attr.bbox.transform.dirty = true;
            }
            oldTransformations.call(sprite, force);
        };
 
        me.sprites.push(sprite);
        me.map[sprite.id] = sprite.getId();
        attr.bbox.plain.dirty = true;
        attr.bbox.transform.dirty = true;
 
        return sprite;
    },
 
    removeSprite: function (sprite, isDestroy) {
        var me = this,
            id, isOwnSprite;
 
        if (sprite) {
            if (sprite.charAt) { // is String
                sprite = me.map[sprite];
            }
            if (!sprite || !sprite.isSprite) {
                return null;
            }
            if (sprite.isDestroyed || sprite.isDestroying) {
                return sprite;
            }
            id = sprite.getId();
            isOwnSprite = me.map[id];
            delete me.map[id];
 
            if (isDestroy) {
                sprite.destroy();
            }
            if (!isOwnSprite) {
                return sprite;
            }
            sprite.setParent(null);
            sprite.setSurface(null);
            Ext.Array.remove(me.sprites, sprite);
 
            me.dirtyZIndex = true;
            me.setDirty(true);
        }
 
        return sprite || null;
    },
 
    updateSurface: function (surface) {
        for (var i = 0, ln = this.sprites.length; i < ln; i++) {
            this.sprites[i].setSurface(surface);
        }
    },
 
    /**
     * Adds a list of sprites to the composite.
     * @param {Ext.draw.sprite.Sprite[]|Object[]|Ext.draw.sprite.Sprite|Object} sprites
     */
    addAll: function (sprites) {
        if (sprites.isSprite || sprites.type) {
            this.add(sprites);
        } else if (Ext.isArray(sprites)) {
            var i = 0;
            while (< sprites.length) {
                this.add(sprites[i++]);
            }
        }
    },
 
    /**
     * Updates the bounding box of the composite, which contains the bounding box of all sprites in the composite.
     */
    updatePlainBBox: function (plain) {
        var me = this,
            left = Infinity,
            right = -Infinity,
            top = Infinity,
            bottom = -Infinity,
            sprite, bbox, i, ln;
 
        for (= 0, ln = me.sprites.length; i < ln; i++) {
            sprite = me.sprites[i];
            sprite.applyTransformations();
            bbox = sprite.getBBox();
            if (left > bbox.x) {
                left = bbox.x;
            }
            if (right < bbox.x + bbox.width) {
                right = bbox.x + bbox.width;
            }
            if (top > bbox.y) {
                top = bbox.y;
            }
            if (bottom < bbox.y + bbox.height) {
                bottom = bbox.y + bbox.height;
            }
        }
        plain.x = left;
        plain.y = top;
        plain.width = right - left;
        plain.height = bottom - top;
    },
 
    isVisible: function () {
        // Override the abstract Sprite's method.
        // Composite uses a simpler check, because it has no fill or stroke
        // style of its own, it just houses other sprites.
        var attr = this.attr,
            parent = this.getParent(),
            hasParent = parent && (parent.isSurface || parent.isVisible()),
            isSeen = hasParent && !attr.hidden && attr.globalAlpha;
 
        return !!isSeen;
    },
 
    /**
     * Renders all sprites contained in the composite to the surface.
     */
    render: function (surface, ctx, rect) {
        var me = this,
            attr = me.attr,
            mat = me.attr.matrix,
            sprites = me.sprites,
            ln = sprites.length,
            i = 0;
 
        mat.toContext(ctx);
        for (; i < ln; i++) {
            surface.renderSprite(sprites[i], rect);
        }
        //<debug>
        var debug = attr.debug || me.statics().debug || Ext.draw.sprite.Sprite.debug;
        if (debug) {
            attr.inverseMatrix.toContext(ctx);
            debug.bbox && me.renderBBox(surface, ctx);
        }
        //</debug>
    },
 
    destroy: function () {
        var me = this,
            sprites = me.sprites,
            ln = sprites.length,
            i;
 
        for (= 0; i < ln; i++) {
            sprites[i].destroy();
        }
 
        sprites.length = 0;
 
        me.callParent();
    }
 
});