/**
 * @class Ext.chart.CartesianChart
 * @extends Ext.chart.AbstractChart
 * @xtype cartesian
 *
 * Represents a chart that uses cartesian coordinates.
 * A cartesian chart has 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 direction by setting the {@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.
 *
 */
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,
 
        innerRect: [0, 0, 1, 1],
 
        /**
         * @cfg {Object} innerPadding The amount of inner padding in pixels.
         * Inner padding is the padding from the innermost axes to the series.
         */
        innerPadding: {
            top: 0,
            left: 0,
            right: 0,
            bottom: 0
        }
    },
    xtype: [ 'cartesian', 'chart' ],
 
    applyInnerPadding: function (padding, oldPadding) {
        if (!Ext.isObject(padding)) {
            return Ext.util.Format.parseBox(padding);
        } else if (!oldPadding) {
            return padding;
        } else {
            return Ext.apply(oldPadding, padding);
        }
    },
 
    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 () {
        this.resizing++;
 
        this.callParent();
        this.suspendThicknessChanged();
        var me = this,
            chartRect = me.getSurface('chart').getRect(),
            width = chartRect[2],
            height = chartRect[3],
            axes = me.getAxes(), axis,
            seriesList = me.getSeries(), series,
            axisSurface, thickness,
            insetPadding = me.getInsetPadding(),
            innerPadding = me.getInnerPadding(),
            surface, gridSurface,
            shrinkBox = Ext.apply({}, insetPadding),
            mainRect, innerWidth, innerHeight,
            elements, floating, floatingValue, matrix, i, ln,
            isRtl = me.getInherited().rtl,
            flipXY = me.getFlipXY();
 
        if (width <= 0 || height <= 0) {
            return;
        }
 
        for (= 0; i < axes.length; i++) {
            axis = axes[i];
            axisSurface = axis.getSurface();
            floating = axis.getFloating();
            floatingValue = floating ? floating.value : null;
            thickness = axis.getThickness();
            switch (axis.getPosition()) {
                case 'top':
                    axisSurface.setRect([0, shrinkBox.top + 1, width, thickness]);
                    break;
                case 'bottom':
                    axisSurface.setRect([0, height - (shrinkBox.bottom + thickness), width, thickness]);
                    break;
                case 'left':
                    axisSurface.setRect([shrinkBox.left, 0, thickness, height]);
                    break;
                case 'right':
                    axisSurface.setRect([width - (shrinkBox.right + thickness), 0, thickness, height]);
                    break;
            }
            if (floatingValue === null) {
                shrinkBox[axis.getPosition()] += thickness;
            }
        }
 
        width -= shrinkBox.left + shrinkBox.right;
        height -= shrinkBox.top + shrinkBox.bottom;
 
        mainRect = [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.setInnerRect([shrinkBox.left, shrinkBox.top, innerWidth, innerHeight]);
 
        if (innerWidth <= 0 || innerHeight <= 0) {
            return;
        }
 
        me.setMainRect(mainRect);
        me.getSurface().setRect(mainRect);
 
        for (= 0, ln = me.surfaceMap.grid && me.surfaceMap.grid.length; i < ln; i++) {
            gridSurface = me.surfaceMap.grid[i];
            gridSurface.setRect(mainRect);
            gridSurface.matrix.set(1, 0, 0, 1, innerPadding.left, innerPadding.top);
            gridSurface.matrix.inverse(gridSurface.inverseMatrix);
        }
 
        for (= 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 (= 0, ln = seriesList.length; i < ln; i++) {
            series = seriesList[i];
            surface = series.getSurface();
            surface.setRect(mainRect);
            if (flipXY) {
                if (isRtl) {
                    surface.matrix.set(0, -1, -1, 0,
                            innerPadding.left + innerWidth,
                            innerPadding.top + innerHeight);
                } else {
                    surface.matrix.set(0, -1, 1, 0,
                        innerPadding.left,
                        innerPadding.top + innerHeight);
                }
            } else {
                surface.matrix.set(1, 0, 0, -1,
                    innerPadding.left,
                    innerPadding.top + innerHeight);
            }
            surface.matrix.inverse(surface.inverseMatrix);
            series.getOverlaySurface().setRect(mainRect);
        }
        me.redraw();
        me.onPlaceWatermark(chartRect[2], chartRect[3]);
 
        this.resizing--;
        this.resumeThicknessChanged();
    },
 
    refloatAxes: function () {
        var me = this,
            axes = me.getAxes(),
            axis, axisSurface, axisRect,
            floating, value, alongAxis, matrix,
            size = me.innerElement.getSize(),
            inset = me.getInsetPadding(),
            width = size.width - inset.left - inset.right,
            height = size.height - inset.top - inset.bottom,
            isHorizontal;
 
        for (var i = 0; i < axes.length; i++) {
            axis = axes[i];
            floating = axis.getFloating();
            value = floating ? floating.value : null;
            if (value !== null) {
                axisSurface = axis.getSurface();
                axisRect = axisSurface.getRect();
                if (!axisRect) {
                    continue;
                }
                axisRect = axisRect.slice();
                alongAxis = me.getAxis(floating.alongAxis);
                if (alongAxis) {
                    isHorizontal = alongAxis.getAlignment() === 'horizontal';
                    if (Ext.isString(value)) {
                        value = alongAxis.getCoordFor(value);
                    }
                    alongAxis.floatingAxes[axis.getId()] = value;
                    matrix = alongAxis.getSprites()[0].attr.matrix;
                    if (isHorizontal) {
                        value = value * matrix.getXX() + matrix.getDX();
                    } else {
                        value = value * matrix.getYY() + matrix.getDY();
                    }
                } else {
                    isHorizontal = axis.getAlignment() === 'horizontal';
                    value = axisSurface.roundPixel(0.01 * value * (isHorizontal ? height : width));
                }
                switch (axis.getPosition()) {
                    case 'top':
                        axisRect[1] = inset.top + value - axisRect[3] + 1;
                        break;
                    case 'bottom':
                        axisRect[1] = inset.top + (alongAxis ? value : height - value);
                        break;
                    case 'left':
                        axisRect[0] = inset.left + value - axisRect[2] + 1;
                        break;
                    case 'right':
                        axisRect[0] = inset.left + (alongAxis ? value : width - value) - 1;
                        break;
                }
                axisSurface.setRect(axisRect);
            }
        }
    },
 
    redraw: function () {
        var me = this,
            series = me.getSeries(),
            axes = me.getAxes(),
            rect = me.getMainRect(),
            innerWidth, innerHeight,
            innerPadding = me.getInnerPadding(),
            sprites, xRange, yRange, isSide, attr, i, j,
            axis, axisX, axisY, range, visibleRange,
            flipXY = me.getFlipXY(),
            sprite, zIndex, zBase = 1000,
            markers, markerCount, markerIndex, markerSprite, markerZIndex;
 
        if (!rect) {
            return;
        }
 
        innerWidth = rect[2] - innerPadding.left - innerPadding.right;
        innerHeight = rect[3] - innerPadding.top - innerPadding.bottom;
        for (= 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();
            }
 
            attr = {
                visibleMinX: xRange[0],
                visibleMaxX: xRange[1],
                visibleMinY: yRange[0],
                visibleMaxY: yRange[1],
                innerWidth: innerWidth,
                innerHeight: innerHeight,
                flipXY: flipXY
            };
 
            sprites = series[i].getSprites();
            for (= 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 (= 0; i < axes.length; i++) {
            axis = axes[i];
            isSide = axis.isSide();
            sprites = axis.getSprites();
            range = axis.getRange();
            visibleRange = axis.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 (= 0; j < sprites.length; j++) {
                sprites[j].setAttributes(attr, true);
            }
        }
        me.renderFrame();
        me.callParent(arguments);
    },
 
    renderFrame: function () {
        this.refloatAxes();
        this.callParent(arguments);
    },
 
    onPlaceWatermark: function (width, height) {
        var me = this,
            watermarkElement = me.watermarkElement,
            rect = watermarkElement &&
                  (me.getSurface ? me.getSurface('main').getRect()
                                 : me.getItems().get(0).getRect());
 
        if (rect) {
            watermarkElement.setStyle({
                right: Math.round(width - (rect[2] + rect[0])) + 'px',
                bottom: Math.round(height - (rect[3] + rect[1])) + 'px'
            });
        }
    }
});