/**
 * @class Ext.chart.CartesianChart
 * @extends Ext.chart.AbstractChart
 *
 * Represents a chart that uses cartesian coordinates.
 * A cartesian chart have two directions, X direction and Y direction.
 * The series and axes are coordinated along these directions.
 * By default the x direction is horizontal and y direction is vertical,
 * You can swap the by setting {@link #flipXY} config to `true`.
 *
 * Cartesian series often treats x direction an y direction differently.
 * In most cases, data on x direction are assumed to be monotonically increasing.
 * Based on this property, cartesian series can be trimmed and summarized properly
 * to gain a better performance.
 *
 * @xtype chart
 */

Ext.define('Ext.chart.CartesianChart', {
    extend: 'Ext.chart.AbstractChart',
    alternateClassName: 'Ext.chart.Chart',
    requires: ['Ext.chart.grid.HorizontalGrid', 'Ext.chart.grid.VerticalGrid'],
    config: {
        /**
         * @cfg {Boolean} flipXY Flip the direction of X and Y axis.
         * If flipXY is true, the X axes will be vertical and Y axes will be horizontal.
         */
        flipXY: false,

        innerRegion: [0, 0, 1, 1]
    },
    xtype: 'chart',
    alias: 'Ext.chart.Chart',

    getDirectionForAxis: function (position) {
        var flipXY = this.getFlipXY();
        if (position === 'left' || position === 'right') {
            if (flipXY) {
                return 'X';
            } else {
                return 'Y';
            }
        } else {
            if (flipXY) {
                return 'Y';
            } else {
                return 'X';
            }
        }
    },

    /**
     * Layout the axes and series.
     */
    performLayout: function () {
        try {
            this.resizing++;
            this.callSuper();
            this.suspendThicknessChanged();
            var me = this,
                axes = me.getAxes(), axis,
                seriesList = me.getSeries(), series,
                axisSurface, thickness,
                size = me.element.getSize(),
                width = size.width,
                height = size.height,
                insetPadding = me.getInsetPadding(),
                innerPadding = me.getInnerPadding(),
                surface,
                shrinkBox = {
                    top: insetPadding.top,
                    left: insetPadding.left,
                    right: insetPadding.right,
                    bottom: insetPadding.bottom
                },
                gridSurface,
                mainRegion, innerWidth, innerHeight,
                elements, floating, matrix, i, ln,
                flipXY = me.getFlipXY();

            if (width <= 0 || height <= 0) {
                return;
            }

            for (i = 0; i < axes.length; i++) {
                axis = axes[i];
                axisSurface = axis.getSurface();
                floating = axis.getStyle && axis.getStyle() && axis.getStyle().floating;
                thickness = axis.getThickness();
                switch (axis.getPosition()) {
                    case 'top':
                        axisSurface.setRegion([0, shrinkBox.top, width, thickness]);
                        break;
                    case 'bottom':
                        axisSurface.setRegion([0, height - (shrinkBox.bottom + thickness), width, thickness]);
                        break;
                    case 'left':
                        axisSurface.setRegion([shrinkBox.left, 0, thickness, height]);
                        break;
                    case 'right':
                        axisSurface.setRegion([width - (shrinkBox.right + thickness), 0, thickness, height]);
                        break;
                }
                if (!floating) {
                    shrinkBox[axis.getPosition()] += thickness;
                }
            }

            width -= shrinkBox.left + shrinkBox.right;
            height -= shrinkBox.top + shrinkBox.bottom;

            mainRegion = [shrinkBox.left, shrinkBox.top, width, height];

            shrinkBox.left += innerPadding.left;
            shrinkBox.top += innerPadding.top;
            shrinkBox.right += innerPadding.right;
            shrinkBox.bottom += innerPadding.bottom;

            innerWidth = width - innerPadding.left - innerPadding.right;
            innerHeight = height - innerPadding.top - innerPadding.bottom;

            me.setInnerRegion([shrinkBox.left, shrinkBox.top, innerWidth, innerHeight]);

            if (innerWidth <= 0 || innerHeight <= 0) {
                return;
            }

            me.setMainRegion(mainRegion);
            me.getSurface('main').setRegion(mainRegion);

            for (i = 0, ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {
                gridSurface = me.surfaceMap.grid[i];
                gridSurface.setRegion(mainRegion);
                gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);
                gridSurface.matrix.inverse(gridSurface.inverseMatrix);
            }

            for (i = 0; i < axes.length; i++) {
                axis = axes[i];
                axisSurface = axis.getSurface();
                matrix = axisSurface.matrix;
                elements = matrix.elements;
                switch (axis.getPosition()) {
                    case 'top':
                    case 'bottom':
                        elements[4] = shrinkBox.left;
                        axis.setLength(innerWidth);
                        break;
                    case 'left':
                    case 'right':
                        elements[5] = shrinkBox.top;
                        axis.setLength(innerHeight);
                        break;
                }
                axis.updateTitleSprite();
                matrix.inverse(axisSurface.inverseMatrix);
            }

            for (i = 0, ln = seriesList.length; i < ln; i++) {
                series = seriesList[i];
                surface = series.getSurface();
                surface.setRegion(mainRegion);
                if (flipXY) {
                    surface.matrix.set(0, -1, 1, 0, innerPadding.left, innerHeight + innerPadding.top);
                } else {
                    surface.matrix.set(1, 0, 0, -1, innerPadding.left, innerHeight + innerPadding.top);
                }
                surface.matrix.inverse(surface.inverseMatrix);
                series.getOverlaySurface().setRegion(mainRegion);
            }
            me.redraw();
            me.onPlaceWatermark();
        } finally {
            this.resizing--;
            this.resumeThicknessChanged();
        }
    },

    redraw: function () {
        var me = this,
            series = me.getSeries(),
            axes = me.getAxes(),
            region = me.getMainRegion(),
            innerWidth, innerHeight,
            innerPadding = me.getInnerPadding(),
            left, right, top, bottom, i, j,
            sprites, xRange, yRange, isSide, attr,
            axisX, axisY, range, visibleRange,
            flipXY = me.getFlipXY(),
            sprite, zIndex, zBase = 1000,
            markers, markerCount, markerIndex, markerSprite, markerZIndex;

        if (!region) {
            return;
        }

        innerWidth = region[2] - innerPadding.left - innerPadding.right;
        innerHeight = region[3] - innerPadding.top - innerPadding.bottom;
        for (i = 0; i < series.length; i++) {
            if ((axisX = series[i].getXAxis())) {
                visibleRange = axisX.getVisibleRange();
                xRange = axisX.getRange();
                xRange = [xRange[0] + (xRange[1] - xRange[0]) * visibleRange[0], xRange[0] + (xRange[1] - xRange[0]) * visibleRange[1]];
            } else {
                xRange = series[i].getXRange();
            }

            if ((axisY = series[i].getYAxis())) {
                visibleRange = axisY.getVisibleRange();
                yRange = axisY.getRange();
                yRange = [yRange[0] + (yRange[1] - yRange[0]) * visibleRange[0], yRange[0] + (yRange[1] - yRange[0]) * visibleRange[1]];
            } else {
                yRange = series[i].getYRange();
            }

            left = xRange[0];
            right = xRange[1];
            top = yRange[0];
            bottom = yRange[1];

            attr = {
                visibleMinX: xRange[0],
                visibleMaxX: xRange[1],
                visibleMinY: yRange[0],
                visibleMaxY: yRange[1],
                innerWidth: innerWidth,
                innerHeight: innerHeight,
                flipXY: flipXY
            };

            sprites = series[i].getSprites();
            for (j = 0; j < sprites.length; j++) {

                // All the series now share the same surface, so we must assign
                // the sprites a zIndex that depends on the index of their series.
                sprite = sprites[j];
                zIndex = (sprite.attr.zIndex || 0);
                if (zIndex < zBase) {
                    // Set the sprite's zIndex
                    zIndex += (i+1) * 100 + zBase;
                    sprite.attr.zIndex = zIndex;
                    // Iterate through its marker sprites to do the same.
                    markers = sprite.boundMarkers;
                    if (markers) {
                        markerCount = (markers.items ? markers.items.length : 0);
                        if (markerCount) {
                            for (markerIndex = 0; markerIndex < markerCount; markerIndex++) {
                                markerSprite = markers.items[markerIndex];
                                markerZIndex = (markerSprite.attr.zIndex || 0);
                                if (markerZIndex == Number.MAX_VALUE) {
                                    markerSprite.attr.zIndex = zIndex;
                                } else {
                                    if (markerZIndex < zBase) {
                                        markerSprite.attr.zIndex = zIndex + markerZIndex;
                                    }
                                }
                            }
                        }
                    }
                }

                sprite.setAttributes(attr, true);
            }
        }

        for (i = 0; i < axes.length; i++) {
            isSide = axes[i].isSide();
            sprites = axes[i].getSprites();
            range = axes[i].getRange();
            visibleRange = axes[i].getVisibleRange();
            attr = {
                dataMin: range[0],
                dataMax: range[1],
                visibleMin: visibleRange[0],
                visibleMax: visibleRange[1]
            };
            if (isSide) {
                attr.length = innerHeight;
                attr.startGap = innerPadding.bottom;
                attr.endGap = innerPadding.top;
            } else {
                attr.length = innerWidth;
                attr.startGap = innerPadding.left;
                attr.endGap = innerPadding.right;
            }
            for (j = 0; j < sprites.length; j++) {
                sprites[j].setAttributes(attr, true);
            }
        }
        me.renderFrame();
        me.callSuper(arguments);
    },

    onPlaceWatermark: function () {
        var region0 = this.element.getBox(),
            region = this.getSurface ? this.getSurface('main').getRegion() : this.getItems().get(0).getRegion();
        if (region) {
            this.watermarkElement.setStyle({
                right: Math.round(region0.width - (region[2] + region[0])) + 'px',
                bottom: Math.round(region0.height - (region[3] + region[1])) + 'px'
            });
        }
    }
});