/**
 * @class Ext.draw.modifier.Highlight
 * @extends Ext.draw.modifier.Modifier
 *
 * Highlight is a modifier that will override sprite attributes
 * with {@link Ext.draw.modifier.Highlight#style style} attributes
 * when sprite's `highlighted` attribute is true.
 */
Ext.define('Ext.draw.modifier.Highlight', {
    extend: 'Ext.draw.modifier.Modifier',
    alias: 'modifier.highlight',
 
    config: {
 
        /**
         * @cfg {Boolean} enabled 'true' if the highlight is applied.
         */
        enabled: false,
 
        /**
         * @cfg {Object} style The style attributes of the highlight modifier.
         */
        style: null
    },
 
    preFx: true,
 
    applyStyle: function (style, oldStyle) {
        oldStyle = oldStyle || {};
 
        if (this.getSprite()) {
            Ext.apply(oldStyle, this.getSprite().self.def.normalize(style));
        } else {
            Ext.apply(oldStyle, style);
        }
        return oldStyle;
    },
 
    prepareAttributes: function (attr) {
        if (!attr.hasOwnProperty('highlightOriginal')) {
            attr.highlighted = false;
            attr.highlightOriginal = Ext.Object.chain(attr);
            attr.highlightOriginal.prototype = attr;
            // A list of attributes that should be removed from a sprite instance
            // when it is unhighlighted.
            attr.highlightOriginal.removeFromInstance = {};
        }
        if (this._lower) {
            this._lower.prepareAttributes(attr.highlightOriginal);
        }
    },
 
    updateSprite: function (sprite, oldSprite) {
        var me = this,
            style = me.getStyle(),
            attributeDefinitions;
 
        if (sprite) {
            attributeDefinitions = sprite.self.def;
            if (style) {
                me._style = attributeDefinitions.normalize(style);
            }
            me.setStyle(sprite.config.highlight);
            // Add highlight related attributes to sprite's attribute definition.
            // This will affect all sprites of the same type, even those without
            // the highlight modifier.
            attributeDefinitions.setConfig({
                defaults: {
                    highlighted: false
                },
                processors: {
                    highlighted: 'bool'
                }
            });
        }
 
        this.setSprite(sprite);
    },
 
    /**
     * @private
     * Filter out modifier changes that override highlight style or source attributes.
     * @param {Object} attr The source attributes.
     * @param {Object} changes The modifier changes.
     * @return {*} The filtered changes.
     */
    filterChanges: function (attr, changes) {
        var me = this,
            highlightOriginal = attr.highlightOriginal,
            style = me.getStyle(),
            name;
 
        if (attr.highlighted) {
            for (name in changes) {
                if (style.hasOwnProperty(name)) {
                    // If sprite is highlighted, then stash the changes
                    // to the `style` attributes made by lower level modifiers
                    // to apply them later when sprite is unhighlighted.
                    highlightOriginal[name] = changes[name];
                    delete changes[name];
                }
            }
        }
 
        return changes;
    },
 
    pushDown: function (attr, changes) {
        var style = this.getStyle(),
            highlightOriginal = attr.highlightOriginal,
            removeFromInstance = highlightOriginal.removeFromInstance,
            highlighted, name, tplAttr, timer;
 
        if (changes.hasOwnProperty('highlighted')) {
            highlighted = changes.highlighted;
            // Hide `highlighted` and `style` from underlying modifiers.
            delete changes.highlighted;
 
            if (this._lower) {
                changes = this._lower.pushDown(highlightOriginal, changes);
            }
            changes = this.filterChanges(attr, changes);
 
            if (highlighted !== attr.highlighted) {
                if (highlighted) {
                    // Switching ON.
                    // At this time, original should be empty.
                    for (name in style) {
                        // Remember the values of attributes to revert back to them on unhighlight.
                        if (name in changes) {
                            // Remember value set by lower level modifiers.
                            highlightOriginal[name] = changes[name];
                        } else {
                            // Remember the original value.
 
                            // If this is a sprite instance and it doesn't have its own
                            // 'name' attribute, (i.e. inherits template's attribute value)
                            // than we have to get the value for the 'name' attribute from
                            // the template's 'targets' object instead of its
                            // 'attr' object (which is the prototype of the instance),
                            // because the 'name' attribute of the template may be animating.
                            // Check out the prepareAttributes method of the Animation
                            // modifier for more details on the 'targets' object.
 
                            tplAttr = attr.template && attr.template.ownAttr;
                            if (tplAttr && !attr.prototype.hasOwnProperty(name)) {
                                removeFromInstance[name] = true;
                                highlightOriginal[name] = tplAttr.targets[name];
                            } else {
 
                                // Even if a sprite instance has its own property, it may
                                // still have to be removed from the instance after
                                // unhighlighting is done.
                                // Consider a situation where an instance doesn't originally
                                // have its own attribute (that is used for highlighting and
                                // unhighlighting). It will however have that attribute as
                                // its own when the highlight/unhighlight animation is in
                                // progress, until the attribute is removed from the instance
                                // when the unhighlighting is done.
                                // So in a scenario where the instance is highlighted, then
                                // unhighlighted (i.e. starts animating back to its original
                                // value) and then highlighted again before the unhighlight
                                // animation is done, we should still mark the attribute
                                // for removal from the instance, if it was our original
                                // intention. To tell if it was, we can check the timer
                                // for the attribute and see if the 'remove' flag is set.
 
                                timer = highlightOriginal.timers[name];
                                if (timer && timer.remove) {
                                    removeFromInstance[name] = true;
                                }
                                highlightOriginal[name] = attr[name];
                            }
                        }
                        if (highlightOriginal[name] !== style[name]) {
                            changes[name] = style[name];
                        }
                    }
                } else {
                    // Switching OFF.
                    for (name in style) {
                        if (!(name in changes)) {
                            changes[name] = highlightOriginal[name];
                        }
                        delete highlightOriginal[name];
                    }
                    changes.removeFromInstance = changes.removeFromInstance || {};
                    // Let the higher lever animation modifier know which attributes
                    // should be removed from instance when the animation is done.
                    Ext.apply(changes.removeFromInstance, removeFromInstance);
                    highlightOriginal.removeFromInstance = {};
                }
                changes.highlighted = highlighted;
            }
        } else {
            if (this._lower) {
                changes = this._lower.pushDown(highlightOriginal, changes);
            }
            changes = this.filterChanges(attr, changes);
        }
 
        return changes;
    },
 
    popUp: function (attr, changes) {
        changes = this.filterChanges(attr, changes);
        this.callParent([attr, changes]);
    }
 
});