/**
 * @class Ext.chart.Markers
 * @extends Ext.draw.sprite.Instancing
 * 
 * Marker sprite. A specialized version of instancing sprite that groups instances.
 * Putting a marker is grouped by its category id. Clearing removes that category.
 */
Ext.define('Ext.chart.Markers', {
    extend: 'Ext.draw.sprite.Instancing',
    isMarkers: true,
    defaultCategory: 'default',
 
    constructor: function () {
        this.callParent(arguments);
        // `categories` maps category names to a map that maps instance index in category to its global index: 
        // categoryName: {instanceIndexInCategory: globalInstanceIndex} 
        this.categories = {};
        // The `revisions` map keeps revision numbers of instance categories. 
        // When a marker (instance) is put (created or updated), it gets the revision 
        // of the category. When a category is cleared, its revision is incremented, 
        // but its instances are not removed. 
        // An instance is only rendered if its revision matches category revision. 
        // In other words, a marker has to be put again after its category has been cleared 
        // or it won't render. 
        this.revisions = {};
    },
 
    destroy: function () {
        this.categories = null;
        this.revisions = null;
        this.callParent();
    },
 
    getMarkerFor: function (category, index) {
        if (category in this.categories) {
            var categoryInstances = this.categories[category];
            if (index in categoryInstances) {
                return this.get(categoryInstances[index]);
            }
        }
    },
 
    /**
     * Clears the markers in the category.
     * @param {String} category 
     */
    clear: function (category) {
        category = category || this.defaultCategory;
        if (!(category in this.revisions)) {
            this.revisions[category] = 1;
        } else {
            this.revisions[category]++;
        }
    },
 
    clearAll: function () {
        this.callParent();
        this.categories = {};
        this.revisions = {};
    },
 
    /**
     * Puts a marker in the category with additional attributes.
     * @param {String} category 
     * @param {Object} attr 
     * @param {String|Number} index
     * @param {Boolean} [bypassNormalization]
     * @param {Boolean} [keepRevision]
     */
    putMarkerFor: function (category, attr, index, bypassNormalization, keepRevision) {
        category = category || this.defaultCategory;
 
        var me = this,
            categoryInstances = me.categories[category] || (me.categories[category] = {}),
            instance;
 
        if (index in categoryInstances) {
            me.setAttributesFor(categoryInstances[index], attr, bypassNormalization);
        } else {
            categoryInstances[index] = me.getCount(); // get the index of the instance created on next line 
            me.add(attr, bypassNormalization);
        }
        instance = me.get(categoryInstances[index]);
        if (instance) {
            instance.category = category;
            if (!keepRevision) {
                instance.revision = me.revisions[category] || (me.revisions[category] = 1);
            }
        }
    },
 
    /**
     *
     * @param {String} category 
     * @param {Mixed} index 
     * @param {Boolean} [isWithoutTransform]
     */
    getMarkerBBoxFor: function (category, index, isWithoutTransform) {
        if (category in this.categories) {
            var categoryInstances = this.categories[category];
            if (index in categoryInstances) {
                return this.getBBoxFor(categoryInstances[index], isWithoutTransform);
            }
        }
    },
 
    getBBox: function () { return null; },
 
    render: function (surface, ctx, rect) {
        var me = this,
            surfaceRect = surface.getRect(),
            revisions = me.revisions,
            mat = me.attr.matrix,
            template = me.getTemplate(),
            templateAttr = template.attr,
            ln = me.instances.length,
            instance, i;
 
        mat.toContext(ctx);
        template.preRender(surface, ctx, rect);
        template.useAttributes(ctx, surfaceRect);
 
        for (= 0; i < ln; i++) {
            instance = me.get(i);
            if (instance.hidden || instance.revision !== revisions[instance.category]) {
                continue;
            }
            ctx.save();
            template.attr = instance;
            template.useAttributes(ctx, surfaceRect);
            template.render(surface, ctx, rect);
            ctx.restore();
        }
 
        template.attr = templateAttr;
    }
});