/**
 * @class Ext.draw.modifier.Target
 * @extends Ext.draw.modifier.Modifier
 *
 * This is the destination (top) modifier that has to be put at
 * the top of the modifier stack.
 *
 * The Target modifier figures out which updaters have to be called
 * for the changed set of attributes and makes the sprite and its instances (if any)
 * call them.
 */
Ext.define('Ext.draw.modifier.Target', {
    requires: ['Ext.draw.Matrix'],
    extend: 'Ext.draw.modifier.Modifier',
    alias: 'modifier.target',
    statics: {
        /**
         * @private
         */
        uniqueId: 0
    },
 
    prepareAttributes: function(attr) {
        if (this._lower) {
            this._lower.prepareAttributes(attr);
        }
 
        attr.attributeId = 'attribute-' + Ext.draw.modifier.Target.uniqueId++;
 
        if (!attr.hasOwnProperty('canvasAttributes')) {
            attr.bbox = {
                plain: { dirty: true },
                transform: { dirty: true }
            };
            
            attr.dirty = true;
            
            /*
            Maps updaters that have to be called to the attributes that triggered the update.
            It is basically a reversed `triggers` map (see Ext.draw.sprite.AttributeDefinition),
            but only for those attributes that have changed.
            Pending updaters are called by the Ext.draw.sprite.Sprite.callUpdaters method.
 
            The 'canvas' updater is a special kind of updater that is not actually a function
            but a flag indicating that the attribute should be applied directly to a canvas
            context.
            */
            
            attr.pendingUpdaters = {};
            
            /*
            Holds the attributes that triggered the canvas update (attr.pendingUpdaters.canvas).
            Canvas attributes are applied directly to a canvas context
            by the sprite.useAttributes method.
            */
            attr.canvasAttributes = {};
            attr.matrix = new Ext.draw.Matrix();
            attr.inverseMatrix = new Ext.draw.Matrix();
        }
    },
 
    /**
     * @private
     * Applies changes to sprite/instance attributes and determines which updaters
     * have to be called as a result of attributes change.
     * @param {Object} attr The source attributes.
     * @param {Object} changes The modifier changes.
     */
    applyChanges: function(attr, changes) {
        Ext.apply(attr, changes);
 
        // eslint-disable-next-line vars-on-top
        var sprite = this.getSprite(),
            pendingUpdaters = attr.pendingUpdaters,
            triggers = sprite.self.def.getTriggers(),
            updaters, instances, instance,
            name, hasChanges, canvasAttributes,
            i, j, ln;
 
        for (name in changes) {
            hasChanges = true;
 
            if ((updaters = triggers[name])) {
                sprite.scheduleUpdaters(attr, updaters, [name]);
            }
 
            if (attr.template && changes.removeFromInstance && changes.removeFromInstance[name]) {
                delete attr[name];
            }
        }
 
        if (!hasChanges) {
            return;
        }
 
        // This can prevent sub objects to set duplicated attributes to context.
        if (pendingUpdaters.canvas) {
            canvasAttributes = pendingUpdaters.canvas;
            delete pendingUpdaters.canvas;
 
            for (= 0, ln = canvasAttributes.length; i < ln; i++) {
                name = canvasAttributes[i];
                attr.canvasAttributes[name] = attr[name];
            }
        }
 
        // If the attributes of an instancing sprite template are being modified here,
        // then spread the pending updaters to the instances (template's children).
        if (attr.hasOwnProperty('children')) {
            instances = attr.children;
 
            for (= 0, ln = instances.length; i < ln; i++) {
                instance = instances[i];
                Ext.apply(instance.pendingUpdaters, pendingUpdaters);
 
                if (canvasAttributes) {
                    for (= 0; j < canvasAttributes.length; j++) {
                        name = canvasAttributes[j];
                        instance.canvasAttributes[name] = instance[name];
                    }
                }
 
                sprite.callUpdaters(instance);
            }
        }
 
        sprite.setDirty(true);
        sprite.callUpdaters(attr);
    },
 
    popUp: function(attr, changes) {
        this.applyChanges(attr, changes);
    },
 
    pushDown: function(attr, changes) {
        // Modifier chain looks like this:
        // sprite.modifiers.target <---> postFx <---> sprite.modifiers.animation <---> preFx
 
        // There can be any number of postFx and preFx modifiers, the difference between them
        // is that:
        // `preFx` modifier changes are animated.
        // `postFx` modifier changes are not.
 
        // preFx modifiers include Highlight (Draw) and Callout (Charts).
        // There are no postFx modifiers at the moment.
 
        if (this._lower) {
            // Without any postFx modifiers, `lower` is going to be Animation.
            changes = this._lower.pushDown(attr, changes);
        }
 
        this.applyChanges(attr, changes);
 
        return changes;
    }
});