/**
 * @class Ext.chart.series.Pie
 * @extends Ext.chart.series.Polar
 *
 * Creates a Pie Chart. A Pie Chart is a useful visualization technique to display quantitative information for different
 * categories that also have a meaning as a whole.
 * As with all other series, the Pie Series must be appended in the *series* Chart array configuration. See the Chart
 * documentation for more information. A typical configuration object for the pie series could be:
 *
 *     @example
 *     Ext.create('Ext.Container', {
 *         renderTo: Ext.getBody(),
 *         width: 600,
 *         height: 400,
 *         layout: 'fit',
 *         items: {
 *             xtype: 'polar',
 *             interactions: 'rotate',
 *             store: {
 *               fields: ['name', 'data1', 'data2', 'data3', 'data4', 'data5'],
 *               data: [
 *                   {name: 'metric one',   data1: 10, data2: 12, data3: 14, data4: 8,  data5: 13},
 *                   {name: 'metric two',   data1: 7,  data2: 8,  data3: 16, data4: 10, data5: 3},
 *                   {name: 'metric three', data1: 5,  data2: 2,  data3: 14, data4: 12, data5: 7},
 *                   {name: 'metric four',  data1: 2,  data2: 14, data3: 6,  data4: 1,  data5: 23},
 *                   {name: 'metric five',  data1: 27, data2: 38, data3: 36, data4: 13, data5: 33}
 *               ]
 *             },
 *             series: {
 *                 type: 'pie',
 *                 label: {
 *                     field: 'name',
 *                     display: 'rotate'
 *                 },
 *                 xField: 'data3',
 *                 donut: 30
 *             }
 *         }
 *     });
 *
 * In this configuration we set `pie` as the type for the series, set an object with specific style properties for highlighting options
 * (triggered when hovering elements). We also set true to `showInLegend` so all the pie slices can be represented by a legend item.
 * We set `data1` as the value of the field to determine the angle span for each pie slice. We also set a label configuration object
 * where we set the field name of the store field to be rendered as text for the label. The labels will also be displayed rotated.
 *
 */
Ext.define('Ext.chart.series.Pie', {
    extend: 'Ext.chart.series.Polar',
    requires: [
        'Ext.chart.series.sprite.PieSlice'
    ],
    type: 'pie',
    alias: 'series.pie',
    seriesType: 'pieslice',
 
    config: {
        /**
         * @cfg {String} labelField 
         * @deprecated Use {@link Ext.chart.series.Pie#label} instead.
         * The store record field name to be used for the pie slice labels.
         */
        labelField: false,
 
        /**
         * @cfg {String} lengthField 
         * The store record field name to be used for the pie slice lengths.
         * The values bound to this field name must be positive real numbers.
         */
        lengthField: false,
 
        /**
         * @cfg {Number} donut Specifies the radius of the donut hole, as a percentage of the chart's radius.
         * Defaults to 0 (no donut hole).
         */
        donut: 0,
 
        /**
         * @cfg {String} field 
         * @deprecated Use xField directly
         */
        field: null,
 
        /**
         * @cfg {Number} rotation The starting angle of the pie slices.
         */
        rotation: 0,
 
        /**
         * @cfg {Boolean} clockwise 
         * Whether the pie slices are displayed clockwise. Default's true.
         */
        clockwise: true,
 
        /**
         * @cfg {Number} [totalAngle=2*PI] The total angle of the pie series.
         */
        totalAngle: 2 * Math.PI,
 
        /**
         * @cfg {Array} hidden Determines which pie slices are hidden.
         */
        hidden: [],
 
        /**
         * @cfg {Number} Allows adjustment of the radius by a specific percentage.
         */
        radiusFactor: 100,
 
        /**
         * @cfg {Object} Default highlight config for the pie series.
         * Slides highlighted pie sector outward.
         */
        highlightCfg: {
            margin: 20
        },
 
        style: {}
    },
 
    directions: ['X'],
 
    setField: function (f) {
        return this.setXField(f);
    },
 
    getField: function () {
        return this.getXField();
    },
 
    applyLabel: function (newLabel, oldLabel) {
        if (Ext.isObject(newLabel) && !Ext.isString(newLabel.orientation)) {
            // Override default label orientation from '' to 'vertical'. 
            Ext.apply(newLabel = Ext.Object.chain(newLabel), {orientation: 'vertical'});
        }
        if (!oldLabel) {
            oldLabel = new Ext.chart.Markers({zIndex: 10});
            oldLabel.setTemplate(new Ext.chart.label.Label(newLabel));
        } else {
            oldLabel.getTemplate().setAttributes(newLabel);
        }
        return oldLabel;
    },
 
    updateLabelData: function () {
        var me = this,
            store = me.getStore(),
            items = store.getData().items,
            sprites = me.getSprites(),
            labelField = me.getLabel().getTemplate().getField(),
            hidden = me.getHidden(),
            i, ln, labels, sprite;
        if (sprites.length > 0 && labelField) {
            labels = [];
            for (= 0, ln = items.length; i < ln; i++) {
                labels.push(items[i].get(labelField));
            }
            for (= 0, ln = sprites.length; i < ln; i++) {
                sprite = sprites[i];
                sprite.setAttributes({label: labels[i]});
                sprite.putMarker('labels', {hidden: hidden[i]}, sprite.attr.attributeId);
            }
        }
    },
 
    coordinateX: function () {
        var me = this,
            store = me.getStore(),
            items = store.getData().items,
            itemCount = items.length,
            field = me.getXField(),
            lengthField = me.getLengthField(),
            value, sum = 0,
            length, maxLength = 0,
            hidden = me.getHidden(),
            summation = [], i,
            lastAngle = 0,
            totalAngle = me.getTotalAngle(),
            clockwise = me.getClockwise() ? 1 : -1,
            sprites = me.getSprites();
 
        if (!sprites) {
            return;
        }
 
        for (= 0; i < itemCount; i++) {
            value = Math.abs(Number(items[i].get(field))) || 0;
            length = lengthField && Math.abs(Number(items[i].get(lengthField))) || 0;
            if (!hidden[i]) {
                sum += value;
                if (length > maxLength) {
                    maxLength = length;
                }
            }
            summation[i] = sum;
            if (>= hidden.length) {
                hidden[i] = false;
            }
        }
        me.maxLength = maxLength;
 
        if (sum !== 0) {
            sum = totalAngle / sum;
        }
        for (= 0; i < itemCount; i++) {
            sprites[i].setAttributes({
                startAngle: lastAngle,
                endAngle: lastAngle = (sum ? clockwise * summation[i] * sum : 0),
                globalAlpha: 1
            });
        }
        for (; i < me.sprites.length; i++) {
            sprites[i].setAttributes({
                startAngle: totalAngle,
                endAngle: totalAngle,
                globalAlpha: 0
            });
        }
        me.getChart().refreshLegendStore();
    },
 
    updateCenter: function (center) {
        this.setStyle({
            translationX: center[0] + this.getOffsetX(),
            translationY: center[1] + this.getOffsetY()
        });
        this.doUpdateStyles();
    },
 
    updateRadius: function (radius) {
        this.setStyle({
            startRho: radius * this.getDonut() * 0.01,
            endRho: radius * this.getRadiusFactor() * 0.01
        });
        this.doUpdateStyles();
    },
 
    getStyleByIndex: function (i) {
        var me = this,
            items = me.getStore().getData().items,
            lengthField = me.getLengthField(),
            radius = me.getRadius(),
            style, length, startRho, endRho;
        length = lengthField && Math.abs(Number(items[i].get(lengthField))) || 0;
        startRho = radius * me.getDonut() * 0.01;
        endRho = radius * me.getRadiusFactor() * 0.01;
        style = this.callParent([i]);
        style.startRho = startRho;
        style.endRho = me.maxLength ? (startRho + (endRho - startRho) * length / me.maxLength) : endRho;
        return style;
    },
 
    updateDonut: function (donut) {
        var radius = this.getRadius();
        this.setStyle({
            startRho: radius * donut * 0.01,
            endRho: radius * this.getRadiusFactor() * 0.01
        });
        this.doUpdateStyles();
    },
 
    rotationOffset: -0.5 * Math.PI,
 
    updateRotation: function (rotation) {
        this.setStyle({
            // Subtract 90 degrees from rotation, so that `rotation` config's default 
            // zero value makes first pie sector start at noon, rather than 3 o'clock. 
            rotationRads: rotation + this.rotationOffset
        });
        this.doUpdateStyles();
    },
 
    updateTotalAngle: function (totalAngle) {
        this.processData();
    },
 
    getSprites: function () {
        var me = this,
            chart = me.getChart(),
            store = me.getStore();
        if (!chart || !store) {
            return [];
        }
        me.getColors();
        me.getSubStyle();
        var items = store.getData().items,
            length = items.length,
            animation = me.getAnimation() || chart && chart.getAnimation(),
            sprites = me.sprites, sprite,
            spriteIndex = 0, rendererData,
            i, spriteCreated = false,
            label = me.getLabel(),
            labelTpl = label.getTemplate();
 
        rendererData = {
            store: store,
            field: me.getField(),
            series: me
        };
 
        for (= 0; i < length; i++) {
            sprite = sprites[i];
            if (!sprite) {
                sprite = me.createSprite();
                if (me.getHighlight()) {
                    sprite.config.highlight = me.getHighlight();
                    sprite.addModifier('highlight', true);
                }
                if (labelTpl.getField()) {
                    labelTpl.setAttributes({
                        labelOverflowPadding: me.getLabelOverflowPadding()
                    });
                    labelTpl.fx.setCustomDurations({'callout': 200});
                    sprite.bindMarker('labels', label);
                }
                sprite.setAttributes(me.getStyleByIndex(i));
                sprite.rendererData = rendererData;
                sprite.rendererIndex = spriteIndex++;
                spriteCreated = true;
            }
            sprite.fx.setConfig(animation);
        }
        if (spriteCreated) {
            me.doUpdateStyles();
        }
        return me.sprites;
    },
 
    betweenAngle: function (x, a, b) {
        var pp = Math.PI * 2,
            offset = this.rotationOffset;
        if (!this.getClockwise()) {
            x *= -1;
            a *= -1;
            b *= -1;
            a -= offset;
            b -= offset;
        } else {
            a += offset;
            b += offset;
        }
        b -= a;
        x -= a;
        x %= pp;
        b %= pp;
        x += pp;
        b += pp;
        x %= pp;
        b %= pp;
        return x < b;
    },
 
    /**
     * Returns the pie slice for a given angle
     * @param {Number} angle The angle to search for the slice
     * @return {Object} An object containing the reocord, sprite, scope etc.
     */
    getItemForAngle: function (angle) {
        var me = this,
            sprites = me.getSprites(),
            attr;
 
        angle %= Math.PI * 2;
 
        while (angle < 0) {
            angle += Math.PI * 2;
        }
 
        if (sprites) {
            var store  = me.getStore(),
                items  = store.getData().items,
                hidden = me.getHidden(),
                i      = 0,
                ln     = store.getCount();
 
            for (; i < ln; i++) {
                if(!hidden[i]) {
                    // Fortunately, item's id equals its index in the instances list. 
                    attr = sprites[i].attr;
 
                    if (attr.startAngle <= angle &&  attr.endAngle >= angle) {
                        return {
                            series: me,
                            sprite: sprites[i],
                            index: i,
                            record: items[i],
                            field: me.getXField()
                        };
                    }
                }
            }
        }
 
        return null;
    },
 
    getItemForPoint: function (x, y) {
        var me = this,
            sprites = me.getSprites();
        if (sprites) {
            var center = me.getCenter(),
                offsetX = me.getOffsetX(),
                offsetY = me.getOffsetY(),
                originalX = x - center[0] + offsetX,
                originalY = y - center[1] + offsetY,
                store = me.getStore(),
                donut = me.getDonut(),
                records = store.getData().items,
                direction = Math.atan2(originalY, originalX) - me.getRotation(),
                radius = Math.sqrt(originalX * originalX + originalY * originalY),
                startRadius = me.getRadius() * donut * 0.01,
                hidden = me.getHidden(),
                i, ln, attr;
 
            for (= 0, ln = records.length; i < ln; i++) {
                if (!hidden[i]) {
                    // Fortunately, item's id equals its index in the instances list. 
                    attr = sprites[i].attr;
                    if (radius >= startRadius + attr.margin && radius <= attr.endRho + attr.margin) {
                        if (me.betweenAngle(direction, attr.startAngle, attr.endAngle)) {
                            return {
                                series: me,
                                sprite: sprites[i],
                                index: i,
                                record: records[i],
                                field: me.getXField()
                            };
                        }
                    }
                }
            }
            return null;
        }
    },
 
    provideLegendInfo: function (target) {
        var me = this,
            store = me.getStore();
        if (store) {
            var items = store.getData().items,
                labelField = me.getLabel().getTemplate().getField(),
                field = me.getField(),
                hidden = me.getHidden(),
                i, style, fill;
 
            for (= 0; i < items.length; i++) {
                style = me.getStyleByIndex(i);
                fill = style.fillStyle;
                if (Ext.isObject(fill)) {
                    fill = fill.stops && fill.stops[0].color;
                }
                target.push({
                    name: labelField ? String(items[i].get(labelField))  : field + ' ' + i,
                    mark: fill || style.strokeStyle || 'black',
                    disabled: hidden[i],
                    series: me.getId(),
                    index: i
                });
            }
        }
    }
});
 
/*
    Moved TODO comments to bottom:
    TODO: `contrast` is not supported. Should be in the series.label config.
    TODO: We set `contrast` to `true` to flip the color of the label if it is to similar 
    to the background color. Finally, we set the font family
    TODO: and size through the `font` parameter.    
*/