/**
 * @class Ext.chart.interactions.ItemHighlight
 * @extends Ext.chart.interactions.Abstract
 *
 * The 'itemhighlight' interaction allows the user to highlight series items in the chart.
 */
Ext.define('Ext.chart.interactions.ItemHighlight', {
 
    extend: 'Ext.chart.interactions.Abstract',
 
    type: 'itemhighlight',
    alias: 'interaction.itemhighlight',
 
    isItemHighlight: true,
 
    config: {
 
        gestures: {
            tap: 'onTapGesture',
            mousemove: 'onMouseMoveGesture',
            mousedown: 'onMouseDownGesture',
            mouseup: 'onMouseUpGesture',
            mouseleave: 'onMouseUpGesture'
        },
 
        /**
         * @cfg {Boolean} [sticky=false]
         * Disables mouse tracking.
         * Series items will only be highlighted/unhighlighted on mouse click.
         * This config has no effect on touch devices.
         */
        sticky: false,
 
        /**
         * @cfg {Boolean} [multiTooltips=false]
         * Enable displaying multiple tooltips for overlapping or adjacent series items within
         * {@link Ext.chart.series.Line#selectionTolerance} radius.
         * Default is to display a tooltip only for the last series item rendered.
         * When multiple tooltips are displayed, they may overlap partially or completely;
         * it is up to the developer to ensure tooltip positioning is satisfactory.
         * 
         * @since 6.6.0
         */
        multiTooltips: false
    },
 
    constructor: function(config) {
        this.callParent([config]);
 
        this.stickyHighlightItem = null;
        this.tooltipItems = [];
    },
 
    destroy: function() {
        this.stickyHighlightItem = this.tooltipItems = null;
 
        this.callParent();
    },
 
    onMouseMoveGesture: function(e) {
        var me = this,
            tooltipItems = me.tooltipItems,
            isMousePointer = e.pointerType === 'mouse',
            tooltips = [],
            item, oldItem, items, tooltip, oldTooltip, i, len, j, jLen;
 
        if (me.getSticky()) {
            return true;
        }
 
        if (isMousePointer && me.stickyHighlightItem) {
            me.stickyHighlightItem = null;
            me.highlight(null);
        }
 
        if (me.isDragging) {
            if (tooltipItems.length && isMousePointer) {
                me.hideTooltips(tooltipItems);
                tooltipItems.length = 0;
            }
        }
        else if (!me.stickyHighlightItem) {
            if (me.getMultiTooltips()) {
                items = me.getItemsForEvent(e);
            }
            else {
                item = me.getItemForEvent(e);
                items = item ? [item] : [];
            }
 
            for (= 0, len = items.length; i < len; i++) {
                item = items[i];
 
                // Items are returned top to down, so first item is the top one.
                // Chart can only have one highlighted item.
                if (=== 0 && item !== me.getChart().getHighlightItem()) {
                    me.highlight(item);
                    me.sync();
                }
 
                tooltip = item.series.getTooltip();
 
                if (tooltip) {
                    tooltips.push(tooltip);
                }
            }
 
            // sync the last item on mouseleave
            me.highlight(item);
 
            if (isMousePointer) {
                // If we detected a mouse hit, show/refresh the tooltip
                if (items.length) {
                    for (= 0, len = items.length; i < len; i++) {
                        item = items[i];
                        tooltip = item.series.getTooltip();
 
                        if (tooltip) {
                            // If there were different previously active items
                            // that are not going to be included in current active items,
                            // ask them to hide their tooltips. Unless those are
                            // the same tooltip instances that we are about to show,
                            // in which case we are just going to reposition them.
                            for (= 0, jLen = tooltipItems.length; j < jLen; j++) {
                                oldItem = tooltipItems[j];
 
                                if (!Ext.Array.contains(items, oldItem)) {
                                    oldTooltip = oldItem.series.getTooltip();
 
                                    if (!Ext.Array.contains(tooltips, oldTooltip)) {
                                        oldItem.series.hideTooltip(oldItem, true);
                                    }
                                }
                            }
 
                            if (tooltip.getTrackMouse()) {
                                item.series.showTooltip(item, e);
                            }
                            else {
                                me.showUntracked(item);
                            }
                        }
                    }
 
                    me.tooltipItems = items;
                }
                // No mouse hit - schedule a hide for hideDelay ms.
                // If pointer enters another item within that time,
                // there will be no flickery reshow.
                else {
                    me.hideTooltips(tooltipItems);
                    tooltipItems.length = 0;
                }
            }
 
            return false;
        }
    },
 
    highlight: function(item) {
        // This is its own function to make it easier for subclasses
        // to enhance the behavior. An alternative would be to listen
        // for the chart's 'itemhighlight' event.
        this.getChart().setHighlightItem(item);
    },
 
    showTooltip: function(e, item) {
        item.series.showTooltip(item, e);
        Ext.Array.include(this.tooltipItems, item);
    },
 
    showUntracked: function(item) {
        var marker = item.sprite.getMarker(item.category),
            surface, surfaceXY, isInverseY,
            itemBBox, matrix;
 
        if (marker) {
            surface = marker.getSurface();
            isInverseY = surface.matrix.elements[3] < 0;
            surfaceXY = surface.element.getXY();
            itemBBox = Ext.clone(marker.getBBoxFor(item.index));
 
            if (isInverseY) {
                // The item.category for bar series will be 'items'.
                // The item.category for line series will be 'markers'.
                // 'items' are in the 'series' surface, which is flipped vertically
                // for cartesian series.
                // 'markers' are in the 'overlay' surface, which isn't flipped.
                // So for 'markers' we already have the bbox in a coordinate system
                // with the origin at the top-left of the surface, but for 'items'
                // we need to do a conversion.
                if (surface.getInherited().rtl) {
                    matrix = surface.inverseMatrix.clone().flipX()
                                    .translate(item.sprite.attr.innerWidth, 0, true);
                }
                else {
                    matrix = surface.inverseMatrix;
                }
 
                itemBBox = matrix.transformBBox(itemBBox);
            }
 
            itemBBox.x += surfaceXY[0];
            itemBBox.y += surfaceXY[1];
            item.series.showTooltipAt(item,
                                      itemBBox.x + itemBBox.width * 0.5,
                                      itemBBox.y + itemBBox.height * 0.5
            );
        }
    },
 
    onMouseDownGesture: function() {
        this.isDragging = true;
    },
 
    onMouseUpGesture: function() {
        this.isDragging = false;
    },
 
    isSameItem: function(a, b) {
        return a && b && a.series === b.series && a.field === b.field && a.index === b.index;
    },
 
    onTapGesture: function(e) {
        var me = this,
            item;
 
        // A click/tap on an item makes its highlight sticky.
        // It requires another click/tap to unhighlight.
        if (e.pointerType === 'mouse' && !me.getSticky()) {
            return;
        }
 
        item = me.getItemForEvent(e);
 
        if (me.isSameItem(me.stickyHighlightItem, item)) {
            item = null; // toggle
        }
 
        me.stickyHighlightItem = item;
        me.highlight(item);
    },
 
    privates: {
        hideTooltips: function(items, force) {
            var item, i, len;
 
            items = Ext.isArray(items) ? items : [items];
 
            for (= 0, len = items.length; i < len; i++) {
                item = items[i];
 
                if (item && item.series && !item.series.destroyed) {
                    item.series.hideTooltip(item, force);
                }
            }
        }
    }
});