/**
 * @class Ext.sparkline.Bar
 *
 * Plots a bar chart of the values in the passed {@link #values} array.
 *
 * See {@link Ext.sparkline.Base the base class} for a simple example.
 */
Ext.define('Ext.sparkline.Bar', {
    extend: 'Ext.sparkline.BarBase',
    requires: [
        'Ext.sparkline.RangeMap'
    ],
 
    alias: 'widget.sparklinebar',
 
    config: {
 
        /**
         * @cfg {String} barColor
         * The bar color for positive values.
         */
        barColor: '#3366cc',
 
        /**
         * @cfg {String} negBarColor
         * The bar color for negative values.
         */
        negBarColor: '#f44',
 
        /**
         * @cfg {String[]} stackedBarColor
         * An array of colours to use for stacked bar charts. The first series will use
         * the first value in the array, the second series will use the second, etc. 
         */
        stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00', '#dd4477',
                          '#0099c6', '#990099'],
 
        /**
         * @cfg {String} zeroColor
         * The bar color for zero values.
         */
        zeroColor: null,
 
        /**
         * @cfg {String} nullColor
         * The bar color for null values. Usually null values are omitted and not
         * plotted. Setting this config causes a very thin bar to be plotted with the
         * special color in the case that null is a meaningful value in the series.
         */
        nullColor: null,
 
        /**
         * @cfg {Boolean} zeroAxis
         * Centers the Y axis at zero by default.
         */
        zeroAxis: true,
 
        /**
         * @cfg {Number} barWidth
         * The pixel width of bars.
         */
        barWidth: 4,
 
        /**
         * @cfg {Number} barSpacing
         * The pixel spacing between bars.
         */
        barSpacing: 1,
 
        /**
         * @cfg {Number} chartRangeMin
         * The minimum value to use for the range of Y values of the chart - Defaults to
         * the minimum value supplied.
         */
        chartRangeMin: null,
 
        /**
         * @cfg {Number} chartRangeMax The maximum value to use for the range of Y values
         * of the chart - Defaults to the minimum value supplied.
         */
        chartRangeMax: null,
 
        /**
         * @cfg {Boolean} chartRangeClip
         * If true then the y values supplied to plot will be clipped to fall between
         * {@link #chartRangeMin} and {@link #chartRangeMax} - By default
         * chartRangeMin/Max just ensure that the chart spans at least that range of
         * values, but does not constrain it.
         */
        chartRangeClip: false,
 
        /**
         * @cfg colorMap
         * @inheritdoc Ext.sparkline.TriState#cfg-colorMap
         */
        colorMap: null
    },
 
    /**
     * @cfg tipTpl
     * @inheritdoc
     */
    tipTpl: '● {prefix}{value}{suffix}',
 
    remove: function(vals, filter) {
        var result = [],
            i, vl;
 
        for (= 0, vl = vals.length; i < vl; i++) {
            if (vals[i] !== filter) {
                result.push(vals[i]);
            }
        }
 
        return result;
    },
 
    // determine if all values of an array match a value
    // returns true if the array is empty
    all: function(arr, val, ignoreNull) {
        var i;
 
        for (= arr.length; i--;) {
            if (ignoreNull && arr[i] === null) {
                continue;
            }
 
            if (arr[i] !== val) {
                return false;
            }
        }
 
        return true;
    },
 
    applyColorMap: function(colorMap) {
        var me = this;
 
        if (Ext.isArray(colorMap)) {
            me.colorMapByIndex = colorMap;
            me.colorMapByValue = null;
        }
        else {
            me.colorMapByIndex = null;
            me.colorMapByValue = colorMap;
 
            if (me.colorMapByValue && me.colorMapByValue.get == null) {
                me.colorMapByValue = new Ext.sparkline.RangeMap(colorMap);
            }
        }
 
        me.updateConfigChange();
 
        return colorMap;
    },
 
    onUpdate: function() {
        var me = this,
            values = me.values,
            barWidth = me.getBarWidth(),
            barSpacing = me.getBarSpacing(),
            chartRangeMin = me.getChartRangeMin(),
            chartRangeMax = me.getChartRangeMax(),
            chartRangeClip = me.getChartRangeClip(),
            stackMin = Infinity,
            stackMax = -Infinity,
            zeroAxis = me.getZeroAxis(),
            stackTotals = [],
            stackRangesNeg = [],
            isStackString, groupMin, groupMax, stackRanges,
            numValues, i, vlen, range, xAxisOffset, min, max, clipMin, clipMax,
            stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc;
 
        // scan values to determine whether to stack bars
        for (= 0, vlen = values.length; i < vlen; i++) {
            val = values[i];
            isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;
 
            if (isStackString || Ext.isArray(val)) {
                stacked = true;
 
                if (isStackString) {
                    val = values[i] = me.normalizeValues(val.split(':'));
                }
 
                val = me.remove(val, null); // min/max will treat null as zero
                groupMin = Math.min.apply(Math, val);
                groupMax = Math.max.apply(Math, val);
 
                if (groupMin < stackMin) {
                    stackMin = groupMin;
                }
 
                if (groupMax > stackMax) {
                    stackMax = groupMax;
                }
            }
        }
 
        me.stacked = stacked;
        me.regionShapes = {};
        me.totalBarWidth = barWidth + barSpacing;
 
        // set width only while expanding the widget in grouped grid REF#EXTJS-26216
        if (values.length) {
            me.width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
        }
 
        if (chartRangeClip) {
            clipMin = chartRangeMin == null ? -Infinity : chartRangeMin;
            clipMax = chartRangeMax == null ? Infinity : chartRangeMax;
        }
 
        numValues = [];
        stackRanges = stacked ? [] : numValues;
 
        for (= 0, vlen = values.length; i < vlen; i++) {
            if (stacked) {
                vlist = values[i];
                values[i] = svals = [];
                stackTotals[i] = 0;
                stackRanges[i] = stackRangesNeg[i] = 0;
 
                for (= 0, slen = vlist.length; j < slen; j++) {
                    val = svals[j] = chartRangeClip
                        ? Ext.Number.constrain(vlist[j], clipMin, clipMax)
                        : vlist[j];
 
                    if (val !== null) {
                        if (val > 0) {
                            stackTotals[i] += val;
                        }
 
                        if (stackMin < 0 && stackMax > 0) {
                            if (val < 0) {
                                stackRangesNeg[i] += Math.abs(val);
                            }
                            else {
                                stackRanges[i] += val;
                            }
                        }
                        else {
                            stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));
                        }
 
                        numValues.push(val);
                    }
                }
            }
            else {
                val = chartRangeClip
                    ? Ext.Number.constrain(values[i], clipMin, clipMax)
                    : values[i];
 
                val = values[i] = me.normalizeValue(val);
 
                if (val !== null) {
                    numValues.push(val);
                }
            }
        }
 
        me.max = max = Math.max.apply(Math, numValues);
        me.min = min = Math.min.apply(Math, numValues);
        me.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;
        me.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;
 
        if (chartRangeMin != null && (chartRangeClip || chartRangeMin < min)) {
            min = chartRangeMin;
        }
 
        if (chartRangeMax != null && (chartRangeClip || chartRangeMax > max)) {
            max = chartRangeMax;
        }
 
        if (min <= 0 && max >= 0 && zeroAxis) {
            xAxisOffset = 0;
        }
        else if (!zeroAxis) {
            xAxisOffset = min;
        }
        else if (min > 0) {
            xAxisOffset = min;
        }
        else {
            xAxisOffset = max;
        }
 
        me.xAxisOffset = xAxisOffset;
 
        range = stacked
            ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg))
            : max - min;
 
        // as we plot zero/min values a single pixel line, we add a pixel to all other
        // values - Reduce the effective canvas size to suit
        me.canvasHeightEf = (zeroAxis && min < 0) ? me.getHeight() - 2 : me.getHeight() - 1;
 
        if (min < xAxisOffset) {
            yMaxCalc = (stacked && max >= 0) ? stackMax : max;
            yoffset = (yMaxCalc - xAxisOffset) / range * me.getHeight();
 
            if (yoffset !== Math.ceil(yoffset)) {
                me.canvasHeightEf -= 2;
                yoffset = Math.ceil(yoffset);
            }
        }
        else {
            yoffset = me.getHeight();
        }
 
        me.yoffset = yoffset;
        me.range = range;
 
    },
 
    getRegion: function(x, y) {
        var result = Math.floor(/ this.totalBarWidth);
 
        return (result < 0 || result >= this.values.length) ? undefined : result;
    },
 
    getRegionFields: function(region) {
        var values = Ext.Array.from(this.values[region]),
            result = [],
            value, i;
 
        for (= values.length; i--;) {
            value = values[i];
            result.push({
                isNull: value === null,
                value: value,
                color: this.calcColor(i, value, region),
                offset: region
            });
        }
 
        return result;
    },
 
    calcColor: function(stacknum, value, valuenum) {
        var me = this,
            colorMapByIndex = me.colorMapByIndex,
            colorMapByValue = me.colorMapByValue,
            color, newColor,
            zeroColor = me.getZeroColor();
 
        if (this.stacked) {
            color = me.getStackedBarColor();
        }
        else {
            color = (value < 0) ? me.getNegBarColor() : me.getBarColor();
        }
 
        if (value === 0 && zeroColor != null) {
            color = zeroColor;
        }
 
        if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
            color = newColor;
        }
        else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
            color = colorMapByIndex[valuenum];
        }
 
        return Ext.isArray(color) ? color[stacknum % color.length] : color;
    },
 
    /*
     * Render bar(s) for a region
     */
    renderRegion: function(valuenum, highlight) {
        var me = this,
            vals = me.values[valuenum],
            xaxisOffset = me.xAxisOffset,
            range = me.range,
            stacked = me.stacked,
            canvas = me.canvas,
            barWidth = me.getBarWidth(),
            x = valuenum * me.totalBarWidth,
            canvasHeightEf = me.canvasHeightEf,
            yoffset = me.yoffset,
            y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin,
            nullColor = me.getNullColor();
 
        vals = Ext.isArray(vals) ? vals : [vals];
        valcount = vals.length;
        val = vals[0];
        isNull = me.all(vals, null);
        allMin = me.all(vals, xaxisOffset, true);
 
        if (isNull) {
            if (nullColor) {
                color = highlight ? nullColor : me.calcHighlightColor(nullColor, me);
                y = (yoffset > 0) ? yoffset - 1 : yoffset;
                canvas.drawRect(x, y, barWidth - 1, 0, color, color).append();
            }
 
            return;
        }
 
        yoffsetNeg = yoffset;
 
        for (= 0; i < valcount; i++) {
            val = vals[i];
 
            if (stacked && val === xaxisOffset) {
                if (!allMin || minPlotted) {
                    continue;
                }
 
                minPlotted = true;
            }
 
            if (range > 0) {
                height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;
            }
            else {
                height = 1;
            }
 
            if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {
                y = yoffsetNeg;
                yoffsetNeg += height;
            }
            else {
                y = yoffset - height;
                yoffset -= height;
            }
 
            color = me.calcColor(i, val, valuenum);
 
            if (highlight) {
                color = me.calcHighlightColor(color, me);
            }
 
            canvas.drawRect(x, y, barWidth - 1, height - 1, color, color).append();
        }
    }
}, function(cls) {
    cls.onClassCreated(cls);
});