define(function(require) {

    'use strict';

    var zrUtil = require('zrender/core/util');
    var numberUtil = require('../util/number');
    var parsePercent = numberUtil.parsePercent;

    function getSeriesStackId(seriesModel) {
        return seriesModel.get('stack') || '__ec_stack_' + seriesModel.seriesIndex;
    }

    function calBarWidthAndOffset(barSeries, api) {
        // Columns info on each category axis. Key is cartesian name
        var columnsMap = {};

        zrUtil.each(barSeries, function (seriesModel, idx) {
            var cartesian = seriesModel.coordinateSystem;

            var baseAxis = cartesian.getBaseAxis();

            var columnsOnAxis = columnsMap[baseAxis.index] || {
                remainedWidth: baseAxis.getBandWidth(),
                autoWidthCount: 0,
                categoryGap: '20%',
                gap: '30%',
                axis: baseAxis,
                stacks: {}
            };
            var stacks = columnsOnAxis.stacks;
            columnsMap[baseAxis.index] = columnsOnAxis;

            var stackId = getSeriesStackId(seriesModel);

            if (!stacks[stackId]) {
                columnsOnAxis.autoWidthCount++;
            }
            stacks[stackId] = stacks[stackId] || {
                width: 0,
                maxWidth: 0
            };

            var barWidth = seriesModel.get('barWidth');
            var barMaxWidth = seriesModel.get('barMaxWidth');
            var barGap = seriesModel.get('barGap');
            var barCategoryGap = seriesModel.get('barCategoryGap');
            // TODO
            if (barWidth && ! stacks[stackId].width) {
                barWidth = Math.min(columnsOnAxis.remainedWidth, barWidth);
                stacks[stackId].width = barWidth;
                columnsOnAxis.remainedWidth -= barWidth;
            }

            barMaxWidth && (stacks[stackId].maxWidth = barMaxWidth);
            (barGap != null) && (columnsOnAxis.gap = barGap);
            (barCategoryGap != null) && (columnsOnAxis.categoryGap = barCategoryGap);
        });

        var result = {};

        zrUtil.each(columnsMap, function (columnsOnAxis, coordSysName) {

            result[coordSysName] = {};

            var stacks = columnsOnAxis.stacks;
            var baseAxis = columnsOnAxis.axis;
            var bandWidth = baseAxis.getBandWidth();
            var categoryGap = parsePercent(columnsOnAxis.categoryGap, bandWidth);
            var barGapPercent = parsePercent(columnsOnAxis.gap, 1);

            var remainedWidth = columnsOnAxis.remainedWidth;
            var autoWidthCount = columnsOnAxis.autoWidthCount;
            var autoWidth = (remainedWidth - categoryGap)
                / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
            autoWidth = Math.max(autoWidth, 0);

            // Find if any auto calculated bar exceeded maxBarWidth
            zrUtil.each(stacks, function (column, stack) {
                var maxWidth = column.maxWidth;
                if (!column.width && maxWidth && maxWidth < autoWidth) {
                    maxWidth = Math.min(maxWidth, remainedWidth);
                    remainedWidth -= maxWidth;
                    column.width = maxWidth;
                    autoWidthCount--;
                }
            });

            // Recalculate width again
            autoWidth = (remainedWidth - categoryGap)
                / (autoWidthCount + (autoWidthCount - 1) * barGapPercent);
            autoWidth = Math.max(autoWidth, 0);

            var widthSum = 0;
            var lastColumn;
            zrUtil.each(stacks, function (column, idx) {
                if (!column.width) {
                    column.width = autoWidth;
                }
                lastColumn = column;
                widthSum += column.width * (1 + barGapPercent);
            });
            if (lastColumn) {
                widthSum -= lastColumn.width * barGapPercent;
            }

            var offset = -widthSum / 2;
            zrUtil.each(stacks, function (column, stackId) {
                result[coordSysName][stackId] = result[coordSysName][stackId] || {
                    offset: offset,
                    width: column.width
                };

                offset += column.width * (1 + barGapPercent);
            });
        });

        return result;
    }

    /**
     * @param {string} seriesType
     * @param {module:echarts/model/Global} ecModel
     * @param {module:echarts/ExtensionAPI} api
     */
    function barLayoutGrid(seriesType, ecModel, api) {

        var barWidthAndOffset = calBarWidthAndOffset(
            zrUtil.filter(
                ecModel.getSeriesByType(seriesType),
                function (seriesModel) {
                    return !ecModel.isSeriesFiltered(seriesModel)
                        && seriesModel.coordinateSystem
                        && seriesModel.coordinateSystem.type === 'cartesian2d';
                }
            )
        );

        var lastStackCoords = {};

        ecModel.eachSeriesByType(seriesType, function (seriesModel) {

            var data = seriesModel.getData();
            var cartesian = seriesModel.coordinateSystem;
            var baseAxis = cartesian.getBaseAxis();

            var stackId = getSeriesStackId(seriesModel);
            var columnLayoutInfo = barWidthAndOffset[baseAxis.index][stackId];
            var columnOffset = columnLayoutInfo.offset;
            var columnWidth = columnLayoutInfo.width;
            var valueAxis = cartesian.getOtherAxis(baseAxis);

            var barMinHeight = seriesModel.get('barMinHeight') || 0;

            var valueAxisStart = baseAxis.onZero
                ? valueAxis.toGlobalCoord(valueAxis.dataToCoord(0))
                : valueAxis.getGlobalExtent()[0];

            var coords = cartesian.dataToPoints(data, true);
            lastStackCoords[stackId] = lastStackCoords[stackId] || [];

            data.setLayout({
                offset: columnOffset,
                size: columnWidth
            });
            data.each(valueAxis.dim, function (value, idx) {
                // 空数据
                if (isNaN(value)) {
                    return;
                }
                if (!lastStackCoords[stackId][idx]) {
                    lastStackCoords[stackId][idx] = {
                        // Positive stack
                        p: valueAxisStart,
                        // Negative stack
                        n: valueAxisStart
                    };
                }
                var sign = value >= 0 ? 'p' : 'n';
                var coord = coords[idx];
                var lastCoord = lastStackCoords[stackId][idx][sign];
                var x, y, width, height;
                if (valueAxis.isHorizontal()) {
                    x = lastCoord;
                    y = coord[1] + columnOffset;
                    width = coord[0] - lastCoord;
                    height = columnWidth;

                    if (Math.abs(width) < barMinHeight) {
                        width = (width < 0 ? -1 : 1) * barMinHeight;
                    }
                    lastStackCoords[stackId][idx][sign] += width;
                }
                else {
                    x = coord[0] + columnOffset;
                    y = lastCoord;
                    width = columnWidth;
                    height = coord[1] - lastCoord;
                    if (Math.abs(height) < barMinHeight) {
                        // Include zero to has a positive bar
                        height = (height <= 0 ? -1 : 1) * barMinHeight;
                    }
                    lastStackCoords[stackId][idx][sign] += height;
                }

                data.setItemLayout(idx, {
                    x: x,
                    y: y,
                    width: width,
                    height: height
                });
            }, true);

        }, this);
    }

    return barLayoutGrid;
});