/**
 * The Crosshair interaction allows the user to get precise values for a specific point on the chart.
 * The values are obtained by single-touch dragging on the chart.
 *
 *     @example preview
 *     var lineChart = Ext.create('Ext.chart.CartesianChart', {
 *         innerPadding: 20,
 *         interactions: [{
 *             type: 'crosshair',
 *             axes: {
 *                 left: {
 *                     label: {
 *                         fillStyle: 'white'
 *                     },
 *                     rect: {
 *                         fillStyle: 'brown',
 *                         radius: 6
 *                     }
 *                 },
 *                 bottom: {
 *                     label: {
 *                         fontSize: '14px',
 *                         fontWeight: 'bold'
 *                     }
 *                 }
 *             },
 *             lines: {
 *                 horizontal: {
 *                     strokeStyle: 'brown',
 *                     lineWidth: 2,
 *                     lineDash: [20, 2, 2, 2, 2, 2, 2, 2]
 *                 }
 *             }
 *         }],
 *         store: {
 *             fields: ['name', 'data'],
 *             data: [
 *                 {name: 'apple', data: 300},
 *                 {name: 'orange', data: 900},
 *                 {name: 'banana', data: 800},
 *                 {name: 'pear', data: 400},
 *                 {name: 'grape', data: 500}
 *             ]
 *         },
 *         axes: [{
 *             type: 'numeric',
 *             position: 'left',
 *             fields: ['data'],
 *             title: {
 *                 text: 'Value',
 *                 fontSize: 15
 *             },
 *             grid: true,
 *             label: {
 *                 rotationRads: -Math.PI / 4
 *             }
 *         }, {
 *             type: 'category',
 *             position: 'bottom',
 *             fields: ['name'],
 *             title: {
 *                 text: 'Category',
 *                 fontSize: 15
 *             }
 *         }],
 *         series: [{
 *             type: 'line',
 *             style: {
 *                 strokeStyle: 'black'
 *             },
 *             xField: 'name',
 *             yField: 'data',
 *             marker: {
 *                 type: 'circle',
 *                 radius: 5,
 *                 fillStyle: 'lightblue'
 *             }
 *         }]
 *     });
 *     Ext.Viewport.setLayout('fit');
 *     Ext.Viewport.add(lineChart);
 */

Ext.define('Ext.chart.interactions.Crosshair', {

    extend: 'Ext.chart.interactions.Abstract',
    requires: [
        'Ext.chart.grid.HorizontalGrid',
        'Ext.chart.grid.VerticalGrid',
        'Ext.chart.CartesianChart',
        'Ext.chart.axis.layout.Discrete'
    ],

    type: 'crosshair',
    alias: 'interaction.crosshair',

    config: {
        /**
         * @cfg {Object} axes
         * Specifies label text and label rect configs on per axis basis or as a single config for all axes.
         *
         *     {
         *         type: 'crosshair',
         *         axes: {
         *             label: { fillStyle: 'white' },
         *             rect: { fillStyle: 'maroon'}
         *         }
         *     }
         *
         * In case per axis configuration is used, an object with keys corresponding
         * to the {@link Ext.chart.axis.Axis#position position} must be provided.
         *
         *     {
         *         type: 'crosshair',
         *         axes: {
         *             left: {
         *                 label: { fillStyle: 'white' },
         *                 rect: {
         *                     fillStyle: 'maroon',
         *                     radius: 4
         *                 }
         *             },
         *             bottom: {
         *                 label: {
         *                     fontSize: '14px',
         *                     fontWeight: 'bold'
         *                 },
         *                 rect: { fillStyle: 'white' }
         *             }
         *         }
         *
         * If the `axes` config is not specified, the following defaults will be used:
         * - `label` will use values from the {@link Ext.chart.axis.Axis#label label} config.
         * - `rect` will use the 'white' fillStyle.
         */
        axes: {
            top: {label: {}, rect: {}},
            right: {label: {}, rect: {}},
            bottom: {label: {}, rect: {}},
            left: {label: {}, rect: {}}
        },

        /**
         * @cfg {Object} lines
         * Specifies attributes of horizontal and vertical lines that make up the crosshair.
         * If this config is missing, black dashed lines will be used.
         *
         *     {
         *         horizontal: {
         *             strokeStyle: 'red',
         *             lineDash: [] // solid line
         *         },
         *         vertical: {
         *             lineWidth: 2,
         *             lineDash: [15, 5, 5, 5]
         *         }
         *     }
         */
        lines: {
            horizontal: {
                strokeStyle: 'black',
                lineDash: [5, 5]
            },
            vertical: {
                strokeStyle: 'black',
                lineDash: [5, 5]
            }
        },
        gesture: 'drag'
    },

    applyAxes: function (axesConfig, oldAxesConfig) {
        return Ext.merge(oldAxesConfig || {}, axesConfig);
    },

    applyLines: function (linesConfig, oldLinesConfig) {
        return Ext.merge(oldLinesConfig || {}, linesConfig);
    },

    updateChart: function (chart) {
        if (!(chart instanceof Ext.chart.CartesianChart)) {
            throw 'Crosshair interaction can only be used on cartesian charts.';
        }
        this.callParent(arguments);
    },

    getGestures: function () {
        var me = this,
            gestures = {};
        gestures[me.getGesture()] = 'onGesture';
        gestures[me.getGesture() + 'start'] = 'onGestureStart';
        gestures[me.getGesture() + 'end'] = 'onGestureEnd';
        return gestures;
    },

    onGestureStart: function (e) {
        var me = this,
            chart = me.getChart(),
            surface = chart.getSurface('overlay-surface'),
            region = chart.getInnerRegion(),
            chartWidth = region[2],
            chartHeight = region[3],
            xy = chart.element.getXY(),
            x = e.pageX - xy[0] - region[0],
            y = e.pageY - xy[1] - region[1],
            axes = chart.getAxes(),
            axesConfig = me.getAxes(),
            linesConfig = me.getLines(),
            axis, axisSurface, axisRegion, axisWidth, axisHeight, axisPosition,
            axisLabel, labelPadding,
            axisSprite, attr, axisThickness, lineWidth, halfLineWidth,
            i;

        if (x > 0 && x < chartWidth && y > 0 && y < chartHeight) {
            me.lockEvents(me.getGesture());
            me.horizontalLine = surface.add(Ext.apply({
                xclass: 'Ext.chart.grid.HorizontalGrid',
                x: 0,
                y: y,
                width: chartWidth
            }, linesConfig.horizontal));
            me.verticalLine = surface.add(Ext.apply({
                xclass: 'Ext.chart.grid.VerticalGrid',
                x: x,
                y: 0,
                height: chartHeight
            }, linesConfig.vertical));
            me.axesLabels = me.axesLabels || {};
            for (i = 0; i < axes.length; i++) {
                axis = axes[i];
                axisSurface = axis.getSurface();
                axisRegion = axisSurface.getRegion();
                axisSprite = axis.getSprites()[0];
                axisWidth = axisRegion[2];
                axisHeight = axisRegion[3];
                axisPosition = axis.getPosition();
                attr = axisSprite.attr;
                axisThickness = axisSprite.thickness;
                lineWidth = attr.axisLine ? attr.lineWidth : 0;
                halfLineWidth = lineWidth / 2;
                labelPadding = Math.max(attr.majorTickSize, attr.minorTickSize) + lineWidth;

                axisLabel = me.axesLabels[axisPosition] = axisSurface.add({type: 'composite'});
                axisLabel.labelRect = axisLabel.add(Ext.apply({
                    type: 'rect',
                    fillStyle: 'white',
                    x: axisPosition === 'right' ? lineWidth : axisSurface.roundPixel(axisWidth - axisThickness - labelPadding) - halfLineWidth,
                    y: axisPosition === 'bottom' ? lineWidth : axisSurface.roundPixel(axisHeight - axisThickness - labelPadding) - lineWidth,
                    width: axisPosition === 'left' ? axisThickness - halfLineWidth + labelPadding : axisThickness + labelPadding,
                    height: axisPosition === 'top' ? axisThickness + labelPadding : axisThickness + labelPadding
                }, axesConfig.rect || axesConfig[axisPosition].rect));
                axisLabel.labelText = axisLabel.add(Ext.apply(Ext.Object.chain(axis.config.label), axesConfig.label || axesConfig[axisPosition].label, {
                    type: 'text',
                    x: (function () {
                        switch (axisPosition) {
                            case 'left':
                                return axisWidth - labelPadding - halfLineWidth - axisThickness / 2;
                            case 'right':
                                return axisThickness / 2 + labelPadding - halfLineWidth;
                            default:
                                return 0;
                        }
                    })(),
                    y: (function () {
                        switch (axisPosition) {
                            case 'top':
                                return axisHeight - labelPadding - halfLineWidth - axisThickness / 2;
                            case 'bottom':
                                return axisThickness / 2 + labelPadding;
                            default:
                                return 0;
                        }
                    })()
                }));
            }
            return false;
        }

    },

    onGesture: function (e) {
        var me = this;
        if (me.getLocks()[me.getGesture()] !== me) {
            return;
        }
        var chart = me.getChart(),
            surface = chart.getSurface('overlay-surface'),
            region = Ext.Array.slice(chart.getInnerRegion()),
            padding = chart.getInnerPadding(),
            px = padding.left,
            py = padding.top,
            chartWidth = region[2],
            chartHeight = region[3],
            xy = chart.element.getXY(),
            x = e.pageX - xy[0] - region[0],
            y = e.pageY - xy[1] - region[1],
            axes = chart.getAxes(),
            axis, axisPosition, axisAlignment, axisSurface, axisSprite, axisMatrix,
            axisLayoutContext, axisSegmenter,
            axisLabel, labelBBox, textPadding,
            xx, yy, dx, dy,
            xValue, yValue,
            text,
            i;

        if (x < 0) {
            x = 0;
        } else if (x > chartWidth) {
            x = chartWidth;
        }
        if (y < 0) {
            y = 0;
        } else if (y > chartHeight) {
            y = chartHeight;
        }
        x += px;
        y += py;

        for (i = 0; i < axes.length; i++) {
            axis = axes[i];
            axisPosition = axis.getPosition();
            axisAlignment = axis.getAlignment();
            axisSurface = axis.getSurface();
            axisSprite = axis.getSprites()[0];
            axisMatrix = axisSprite.attr.matrix;
            textPadding = axisSprite.attr.textPadding * 2;
            axisLabel = me.axesLabels[axisPosition];
            axisLayoutContext = axisSprite.getLayoutContext();
            axisSegmenter = axis.getSegmenter();

            if (axisLabel) {
                if (axisAlignment === 'vertical') {
                    yy = axisMatrix.getYY();
                    dy = axisMatrix.getDY();
                    yValue = (y - dy - py) / yy;
                    if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
                        y = Math.round(yValue) * yy + dy + py;
                        yValue = axisSegmenter.from(Math.round(yValue));
                        yValue = axisSprite.attr.data[yValue];
                    } else {
                        yValue = axisSegmenter.from(yValue);
                    }
                    text = axisSegmenter.renderer(yValue, axisLayoutContext);

                    axisLabel.setAttributes({translationY: y - py});
                    axisLabel.labelText.setAttributes({text: text});
                    labelBBox = axisLabel.labelText.getBBox();
                    axisLabel.labelRect.setAttributes({
                        height: labelBBox.height + textPadding,
                        y: -(labelBBox.height + textPadding) / 2
                    });
                    axisSurface.renderFrame();
                } else {
                    xx = axisMatrix.getXX();
                    dx = axisMatrix.getDX();
                    xValue = (x - dx - px) / xx;
                    if (axis.getLayout() instanceof Ext.chart.axis.layout.Discrete) {
                        x = Math.round(xValue) * xx + dx + px;
                        xValue = axisSegmenter.from(Math.round(xValue));
                        xValue = axisSprite.attr.data[xValue];
                    } else {
                        xValue = axisSegmenter.from(xValue);
                    }
                    text = axisSegmenter.renderer(xValue, axisLayoutContext);

                    axisLabel.setAttributes({translationX: x - px});
                    axisLabel.labelText.setAttributes({text: text});
                    labelBBox = axisLabel.labelText.getBBox();
                    axisLabel.labelRect.setAttributes({
                        width: labelBBox.width + textPadding,
                        x: -(labelBBox.width + textPadding) / 2
                    });
                    axisSurface.renderFrame();
                }
            }
        }
        me.horizontalLine.setAttributes({y: y});
        me.verticalLine.setAttributes({x: x});
        surface.renderFrame();
        return false;
    },

    onGestureEnd: function (e) {
        var me = this,
            chart = me.getChart(),
            surface =  chart.getSurface('overlay-surface'),
            axes = chart.getAxes(),
            axis, axisPosition, axisSurface, axisLabel,
            i;

        surface.remove(me.verticalLine);
        surface.remove(me.horizontalLine);

        for (i = 0; i < axes.length; i++) {
            axis = axes[i];
            axisPosition = axis.getPosition();
            axisSurface = axis.getSurface();
            axisLabel = me.axesLabels[axisPosition];
            if (axisLabel) {
                delete me.axesLabels[axisPosition];
                axisSurface.remove(axisLabel);
            }
            axisSurface.renderFrame();
        }

        surface.renderFrame();
        me.unlockEvents(me.getGesture());
    }

});