/**
 * @class Ext.sparkline.Box
 * Generates a box plot graph from the provided {@link #values} array.
 *
 * See <a href="http://en.wikipedia.org/wiki/Box_plot">Wikipedia Box Plots</a>
 *
 * See {@link Ext.sparkline.Base the base class} for a simple example.
 */
Ext.define('Ext.sparkline.Box', {
    extend: 'Ext.sparkline.Base',
 
    alias: 'widget.sparklinebox',
 
    config: {
 
        /**
         * @cfg {Boolean} [raw=false] By default the points are calculated from the
         * input values array. Set this to true to pass the pre-calculated points
         * in the values config.
         */
        raw: false,
 
        /**
         * @cfg {String} [boxLineColor=#000] The color of the box outline.
         */
        boxLineColor: '#000',
 
        /**
         * @cfg {String} [boxFillColor=#cdf] The color of the box fill.
         */
        boxFillColor: '#cdf',
 
        /**
         * @cfg {String} [whiskerColor=#000] The color of the whiskers.
         */
        whiskerColor: '#000',
 
        /**
         * @cfg {String} [outlierLineColor=#333] The color of the outlier circles' outline.
         */
        outlierLineColor: '#333',
 
        /**
         * @cfg {String} [outlierFillColor=#fff] The fill color of the outlier circles.
         */
        outlierFillColor: '#fff',
 
        /**
         * @cfg {String} [medianColor=#f00] The color of the median line.
         */
        medianColor: '#f00',
 
        /**
         * @cfg {Boolean} [showOutliers=true] Configure as `false` to not show outlier circles.
         */
        showOutliers: true,
 
        /**
         * @cfg {Number} [outlierIQR=1.5] The inter-quartile range multiplier used to calculate
         * values that qualify as an outlier.
         */
        outlierIQR: 1.5,
 
        /**
         * @cfg {Number} [spotRadius=1.5] The radius of the outlier circles.
         */
        spotRadius: 1.5,
 
        /**
         * @cfg {Number} [target] If set, a crosshair will be drawn at the specified value point.
         */
        target: null,
 
        /**
         * @cfg {String} [targetColor=#4a2] The color of the crosshair drawn at the point
         * specified by {@link #target}.
         */
        targetColor: '#4a2',
 
        /**
         * @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
    },
 
    tipTpl: ['{field:this.fields}: {value}', {
        fields: function(v) {
            var fields = {
                lq: 'Lower Quartile',
                med: 'Median',
                uq: 'Upper Quartile',
                lo: 'Left Outlier',
                ro: 'Right Outlier',
                lw: 'Left Whisker',
                rw: 'Right Whisker'
            };
 
            return fields[v];
        }
    }],
    tooltipFormatFieldlistKey: 'field',
 
    quartile: function(values, q) {
        var vl;
 
        if (=== 2) {
            vl = Math.floor(values.length / 2);
 
            return values.length % 2 ? values[vl] : (values[vl - 1] + values[vl]) / 2;
        }
        else {
            if (values.length % 2) { // odd
                vl = (values.length * q + q) / 4;
 
                return vl % 1
                    ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2
                    : values[vl - 1];
            }
            else { // even
                vl = (values.length * q + 2) / 4;
 
                return vl % 1
                    ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2
                    : values[vl - 1];
 
            }
        }
    },
 
    // Ensure values is an array of numbers
    applyValues: function(newValues) {
        newValues = Ext.Array.map(Ext.Array.from(newValues), Number);
 
        // If we are calculating the values, they need to be sorted
        if (!this.raw) {
            newValues.sort(function(a, b) {
                return a - b;
            });
        }
 
        this.disabled = !(newValues && newValues.length);
        this.updateConfigChange();
 
        return newValues;
    },
 
    /*
     * Simulate a single region
     */
    getRegion: function() {
        return 1;
    },
 
    getRegionFields: function() {
        var result = [
            { field: 'lq', value: this.quartiles[0] },
            { field: 'med', value: this.quartiles[1] },
            { field: 'uq', value: this.quartiles[2] }
        ];
 
        if (this.loutlier !== undefined) {
            result.push({ field: 'lo', value: this.loutlier });
        }
 
        if (this.routlier !== undefined) {
            result.push({ field: 'ro', value: this.routlier });
        }
 
        if (this.lwhisker !== undefined) {
            result.push({ field: 'lw', value: this.lwhisker });
        }
 
        if (this.rwhisker !== undefined) {
            result.push({ field: 'rw', value: this.rwhisker });
        }
 
        return result;
    },
 
    renderHighlight: Ext.emptyFn,
 
    renderGraph: function() {
        var me = this,
            canvas = me.canvas,
            values = me.values,
            vlen = values.length,
            canvasWidth = me.getWidth(),
            canvasHeight = me.getHeight(),
            chartRangeMin = me.getChartRangeMin(),
            chartRangeMax = me.getChartRangeMax(),
            minValue = chartRangeMin == null ? Math.min.apply(Math, values) : chartRangeMin,
            maxValue = chartRangeMax == null ? Math.max.apply(Math, values) : chartRangeMax,
            canvasLeft = 0,
            lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,
            size, unitSize,
            spotRadius = me.getSpotRadius(),
            outlierLineColor = me.getOutlierLineColor(),
            outlierFillColor = me.getOutlierFillColor(),
            showOutliers = me.getShowOutliers(),
            outlierIQR = me.getOutlierIQR(),
            lineColor = me.getLineColor(),
            whiskerColor = me.getWhiskerColor(),
            targetColor = me.getTargetColor();
 
        if (!me.callParent()) {
            return;
        }
 
        if (me.raw) {
            if (showOutliers && values.length > 5) {
                loutlier = values[0];
                lwhisker = values[1];
                q1 = values[2];
                q2 = values[3];
                q3 = values[4];
                rwhisker = values[5];
                routlier = values[6];
            }
            else {
                lwhisker = values[0];
                q1 = values[1];
                q2 = values[2];
                q3 = values[3];
                rwhisker = values[4];
            }
        }
        else {
            q1 = me.quartile(values, 1);
            q2 = me.quartile(values, 2);
            q3 = me.quartile(values, 3);
            iqr = q3 - q1;
 
            if (showOutliers) {
                lwhisker = rwhisker = null;
 
                for (= 0; i < vlen; i++) {
                    if (lwhisker == null && values[i] > q1 - (iqr * outlierIQR)) {
                        lwhisker = values[i];
                    }
 
                    if (values[i] < q3 + (iqr * outlierIQR)) {
                        rwhisker = values[i];
                    }
                }
 
                loutlier = values[0];
                routlier = values[vlen - 1];
            }
            else {
                lwhisker = values[0];
                rwhisker = values[vlen - 1];
            }
        }
 
        me.quartiles = [q1, q2, q3];
        me.lwhisker = lwhisker;
        me.rwhisker = rwhisker;
        me.loutlier = loutlier;
        me.routlier = routlier;
 
        unitSize = canvasWidth / (maxValue - minValue + 1);
 
        if (showOutliers) {
            canvasLeft = Math.ceil(spotRadius);
            canvasWidth -= 2 * Math.ceil(spotRadius);
            unitSize = canvasWidth / (maxValue - minValue + 1);
 
            if (loutlier < lwhisker) {
                canvas.drawCircle((loutlier - minValue) * unitSize + canvasLeft,
                                  canvasHeight / 2,
                                  spotRadius,
                                  outlierLineColor,
                                  outlierFillColor).append();
            }
 
            if (routlier > rwhisker) {
                canvas.drawCircle((routlier - minValue) * unitSize + canvasLeft,
                                  canvasHeight / 2,
                                  spotRadius,
                                  outlierLineColor,
                                  outlierFillColor).append();
            }
        }
 
        // box
        canvas.drawRect(
            Math.round((q1 - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight * 0.1),
            Math.round((q3 - q1) * unitSize),
            Math.round(canvasHeight * 0.8),
            me.getBoxLineColor(),
            me.getBoxFillColor()).append();
        // left whisker
        canvas.drawLine(
            Math.round((lwhisker - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight / 2),
            Math.round((q1 - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight / 2),
            lineColor).append();
        canvas.drawLine(
            Math.round((lwhisker - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight / 4),
            Math.round((lwhisker - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight - canvasHeight / 4),
            whiskerColor).append();
        // right whisker
        canvas.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),
                        Math.round(canvasHeight / 2),
                        Math.round((q3 - minValue) * unitSize + canvasLeft),
                        Math.round(canvasHeight / 2),
                        lineColor).append();
        canvas.drawLine(
            Math.round((rwhisker - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight / 4),
            Math.round((rwhisker - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight - canvasHeight / 4),
            whiskerColor).append();
        // median line
        canvas.drawLine(
            Math.round((q2 - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight * 0.1),
            Math.round((q2 - minValue) * unitSize + canvasLeft),
            Math.round(canvasHeight * 0.9),
            me.getMedianColor()).append();
 
        if (me.target) {
            size = Math.ceil(me.spotRadius);
            canvas.drawLine(
                Math.round((me.target - minValue) * unitSize + canvasLeft),
                Math.round((canvasHeight / 2) - size),
                Math.round((me.target - minValue) * unitSize + canvasLeft),
                Math.round((canvasHeight / 2) + size),
                targetColor).append();
            canvas.drawLine(
                Math.round((me.target - minValue) * unitSize + canvasLeft - size),
                Math.round(canvasHeight / 2),
                Math.round((me.target - minValue) * unitSize + canvasLeft + size),
                Math.round(canvasHeight / 2),
                targetColor).append();
        }
 
        // If mouse is over, re-apply the highlight
        if (me.currentPageXY && me.canvasRegion.contains(me.currentPageXY)) {
            me.currentRegion = null;
            me.updateDisplay();
        }
 
        canvas.render();
    }
});