/**
 * @class Ext.draw.sprite.Composite
 * 
 * Represents a group of sprites.
 * Composite's sprites are rendered in the order they've been added to the Composite.
 * The rendering order of composite sprites themselves is determined by the value of
 * their zIndex attribute, just like with any other sprite.
 * Every sprite that is added to the Composite is removed from whatever Surface/Composite
 * it belongs to.
 */
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 sprite(s) to the composite.
     * @param {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]/Object/Object[]} sprite
     * @return {Ext.draw.sprite.Sprite/Ext.draw.sprite.Sprite[]}
     */
    addSprite: function(sprite) {
        var i = 0,
            attr, results, oldTransformations;
 
        if (Ext.isArray(sprite)) {
            results = [];
 
            while (< sprite.length) {
                results.push(this.addSprite(sprite[i++]));
            }
 
            return results;
        }
 
        if (sprite && sprite.type && !sprite.isSprite) {
            sprite = Ext.create('sprite.' + sprite.type, sprite);
        }
 
        if (!sprite || !sprite.isSprite || sprite.isComposite) {
            return null;
        }
 
        sprite.setSurface(null);
        sprite.setParent(this);
 
        attr = this.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);
        };
 
        this.sprites.push(sprite);
        this.map[sprite.id] = sprite.getId();
        attr.bbox.plain.dirty = true;
        attr.bbox.transform.dirty = true;
 
        return sprite;
    },
 
    /**
     * @deprecated 6.2.1 Use {@link #addSprite} instead.
     */
    add: function(sprite) {
        return this.addSprite(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.destroyed || sprite.destroying) {
                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;
    },
 
    /**
     * @deprecated 6.2.1 Use {@link #addSprite} instead.
     * Adds a list of sprites to the composite.
     * @param {Ext.draw.sprite.Sprite[]|Object[]|Ext.draw.sprite.Sprite|Object} sprites
     */
    addAll: function(sprites) {
        var i = 0;
 
        if (sprites.isSprite || sprites.type) {
            this.add(sprites);
        }
        else if (Ext.isArray(sprites)) {
            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>
        // eslint-disable-next-line vars-on-top
        var debug = attr.debug || me.statics().debug || Ext.draw.sprite.Sprite.debug;
 
        if (debug) {
            attr.inverseMatrix.toContext(ctx);
 
            if (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();
    }
});