/**
 * @class Ext.draw.sprite.Instancing
 * @extends Ext.draw.sprite.Sprite
 *
 * Sprite that represents multiple instances based on the given template.
 */
Ext.define('Ext.draw.sprite.Instancing', {
    extend: 'Ext.draw.sprite.Sprite',
    alias: 'sprite.instancing',
    type: 'instancing',
    isInstancing: true,
    config: {
        
        /**
         * @cfg {Object} [template=null] The sprite template used by all instances.
         */
        template: null
    },
    instances: null,
 
    applyTemplate: function (template) {
        //<debug> 
        if (!Ext.isObject(template)) {
            Ext.Error.raise("A template of an instancing sprite must either be " +
                "a sprite instance or a valid config object from which a template " +
                "sprite will be created.");
        } else if (template.isInstancing || template.isComposite) {
            Ext.Error.raise("Can't use an instancing or composite sprite " +
                "as a template for an instancing sprite.");
        }
        //</debug> 
        if (!template.isSprite) {
            if (!template.xclass && !template.type) {
                // For compatibility with legacy charts. 
                template.type = 'circle';
            }
            template = Ext.create(template.xclass || 'sprite.' + template.type, template);
        }
        template.setParent(this);
        return template;
    },
 
    updateTemplate: function (template, oldTemplate) {
        if (oldTemplate) {
            delete oldTemplate.ownAttr;
        }
        template.setSurface(this.getSurface());
        // ownAttr is used to get a reference to the template's attributes 
        // when one of the instances is rendering, as at that moment the template's 
        // attributes (template.attr) are the instance's attributes. 
        template.ownAttr = template.attr;
        template.attr.children = this.instances = [];
        this.position = 0;
    },
 
    updateSurface: function (surface) {
        var template = this.getTemplate();
        if (template) {
            template.setSurface(surface);
        }
    },
 
    get: function (index) {
        return this.instances[index];
    },
 
    getCount: function () {
        return this.instances.length;
    },
 
    clearAll: function () {
        this.instances.length = 0;
        this.position = 0;
    },
 
    /**
     * @private
     * Checks if the instancing sprite can be seen.
     * @return {Boolean}
     */
    isVisible: function () {
        var attr = this.attr,
            parent = this.getParent(),
            result;
 
        result = parent && parent.isSurface && !attr.hidden && attr.globalAlpha;
 
        return !!result;
    },
 
    /**
     * Creates a new sprite instance.
     * 
     * @param {Object} config The configuration of the instance.
     * @param {Boolean} [bypassNormalization] 'true' to bypass attribute normalization.
     * @param {Boolean} [avoidCopy] 'true' to avoid copying the `config` object.
     * @return {Object} The attributes of the instance.
     */
    createInstance: function (config, bypassNormalization, avoidCopy) {
        var template = this.getTemplate(),
            originalAttr = template.attr,
            attr = Ext.Object.chain(originalAttr);
        template.topModifier.prepareAttributes(attr);
        template.attr = attr;
        template.setAttributes(config, bypassNormalization, avoidCopy);
        attr.template = template;
        this.instances.push(attr);
        template.attr = originalAttr;
        this.position++;
        return attr;
    },
 
    /**
     * Not supported.
     * 
     * @return {null}
     */
    getBBox: function () { return null; },
 
    /**
     * Returns the bounding box for the instance at the given index.
     *
     * @param {Number} index The index of the instance.
     * @param {Boolean} [isWithoutTransform] 'true' to not apply sprite transforms to the bounding box.
     * @return {Object} The bounding box for the instance.
     */
    getBBoxFor: function (index, isWithoutTransform) {
        var template = this.getTemplate(),
            originalAttr = template.attr,
            bbox;
        template.attr = this.instances[index];
        bbox = template.getBBox(isWithoutTransform);
        template.attr = originalAttr;
        return bbox;
    },
 
    render: function (surface, ctx, clipRect, rect) {
        //<debug> 
        if (!this.getTemplate()) {
            Ext.Error.raise('An instancing sprite must have a template.');
        }
        //</debug> 
        var me = this,
            template = me.getTemplate(),
            mat = me.attr.matrix,
            originalAttr = template.attr,
            instances = me.instances,
            i, ln = me.position;
 
        mat.toContext(ctx);
        template.preRender(surface, ctx, clipRect, rect);
        template.useAttributes(ctx, rect);
        for (= 0; i < ln; i++) {
            if (instances[i].dirtyZIndex) {
                break;
            }
        }
        for (= 0; i < ln; i++) {
            if (instances[i].hidden) {
                continue;
            }
            ctx.save();
            template.attr = instances[i];
            template.useAttributes(ctx, rect);
            template.render(surface, ctx, clipRect, rect);
            ctx.restore();
        }
        template.attr = originalAttr;
    },
 
    /**
     * Sets the attributes for the instance at the given index.
     * 
     * @param {Number} index the index of the instance
     * @param {Object} changes the attributes to change
     * @param {Boolean} [bypassNormalization] 'true' to avoid attribute normalization
     */
    setAttributesFor: function (index, changes, bypassNormalization) {
        var template = this.getTemplate(),
            originalAttr = template.attr,
            attr = this.instances[index];
        if (!attr) {
            return;
        }
        template.attr = attr;
        if (bypassNormalization) {
            changes = Ext.apply({}, changes);
        } else {
            changes = template.self.def.normalize(changes);
        }
        template.topModifier.pushDown(attr, changes);
        template.attr = originalAttr;
    },
 
    destroy: function () {
        this.callParent();
        this.instances.length = 0;
        this.instances = null;
        if (this.getTemplate()) {
            this.getTemplate().destroy();
        }
    }
});