/**
 * @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',
    config: {
        
        /**
         * @cfg {Object} [template=null] The sprite template used by all instances.
         */
        template: null
    },
    instances: null,
    constructor: function (config) {
        this.instances = [];
        this.callSuper([config]);
        if (config && config.template) {
            this.setTemplate(config.template);
        }
    },

    applyTemplate: function (template) {
        if (!(template instanceof Ext.draw.sprite.Sprite)) {
            template = Ext.create(template.xclass || "sprite." + template.type, template);
        }
        template.setParent(this);
        template.attr.children = [];
        this.instances = [];
        this.position = 0;
        return template;
    },

    /**
     * Creates a new sprite instance.
     * 
     * @param {Object} config The configuration of the instance.
     * @param {Object} [data]
     * @param {Boolean} [bypassNormalization] 'true' to bypass attribute normalization.
     * @param {Boolean} [avoidCopy] 'true' to avoid copying.
     * @return {Object} The attributes of the instance.
     */
    createInstance: function (config, data, 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.data = data;
        this.instances.push(attr);
        template.attr = originalAttr;
        this.position++;
        originalAttr.children.push(attr);
        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, clipRegion, region) {
        var me = this,
            mat = me.attr.matrix,
            template = me.getTemplate(),
            originalAttr = template.attr,
            instances = me.instances,
            i, ln = me.position;

        mat.toContext(ctx);
        template.preRender(surface, ctx, clipRegion, region);
        template.useAttributes(ctx, region);
        for (i = 0; i < ln; i++) {
            if (instances[i].dirtyZIndex) {
                break;
            }
        }
        for (i = 0; i < ln; i++) {
            if (instances[i].hidden) {
                continue;
            }
            ctx.save();
            template.attr = instances[i];
            template.applyTransformations();
            template.useAttributes(ctx, region);
            template.render(surface, ctx, clipRegion, region);
            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];
        template.attr = attr;
        try {
            if (bypassNormalization) {
                changes = Ext.apply({}, changes);
            } else {
                changes = template.self.def.normalize(changes);
            }
            template.topModifier.pushDown(attr, changes);
            template.updateDirtyFlags(attr);
        } finally {
            template.attr = originalAttr;
        }
    },

    destroy: function () {
        this.callSuper();
        this.instances.length = 0;
        this.instances = null;
        if (this.getTemplate()) {
            this.getTemplate().destroy();
        }
    }
});