/**
 * @class Ext.chart.axis.layout.Discrete
 * @extends Ext.chart.axis.layout.Layout
 *
 * Simple processor for data that cannot be interpolated.
 */
Ext.define('Ext.chart.axis.layout.Discrete', {
    extend: 'Ext.chart.axis.layout.Layout',
    alias: 'axisLayout.discrete',
    isDiscrete: true,
 
    processData: function() {
        var me = this,
            axis = me.getAxis(),
            seriesList = axis.boundSeries,
            direction = axis.getDirection(),
            i, ln, series;
 
        me.labels = [];
        me.labelMap = {};
 
        for (= 0, ln = seriesList.length; i < ln; i++) {
            series = seriesList[i];
 
            if (series['get' + direction + 'Axis']() === axis) {
                series['coordinate' + direction]();
            }
        }
        // About the labels on Category axes (aka. axes with a Discrete layout)...
        //
        // When the data set from the store changes, series.processData() is called, which does
        // its thing at the series level and then calls series.updateLabelData() to update
        // the labels in the sprites that belong to the series. At the same time,
        // series.processData() calls axis.processData(), which also does its thing but at the axis
        // level, and also needs to update the labels for the sprite(s) that belong to the axis.
        // This is not that simple, however. So how are the axis labels rendered?
        // First, axis.sprite.Axis.render() calls renderLabels() which obtains the majorTicks
        // from the  axis.layout and iterate() through them. The majorTicks are an object returned
        // by snapEnds() below which provides a getLabel() function that returns the label
        // from the axis.layoutContext.data array. So now the question is: how are the labels
        // transferred from the axis.layout to the axis.layoutContext?
        // The easy response is: it's in calculateLayout() below. The issue is to call
        // calculateLayout() because it takes in an axis.layoutContext that can only be created
        // in axis.sprite.Axis.layoutUpdater(), which is a private "updater" function that is
        // called by all the sprite's "triggers". Of course, we don't want to call layoutUpdater()
        // directly from here, so instead we update the sprite's data attribute, which sets
        // the trigger which calls layoutUpdater() which calls calculateLayout() etc...
        // Note that the sprite's data attribute could be set to any value and it would still result
        // in the   trigger we need. For consistency, however, it is set to the labels.
 
        axis.getSprites()[0].setAttributes({ data: me.labels });
        me.fireEvent('datachange', me.labels);
    },
 
    /**
     * @method calculateLayout
     * @inheritdoc
     */
    calculateLayout: function(context) {
        context.data = this.labels;
        this.callParent([context]);
    },
 
    /**
     * @method calculateMajorTicks
     * @inheritdoc
     */
    calculateMajorTicks: function(context) {
        var me = this,
            attr = context.attr,
            data = context.data,
            range = attr.max - attr.min,
            viewMin = attr.min + range * attr.visibleMin,
            viewMax = attr.min + range * attr.visibleMax,
            out;
 
        out = me.snapEnds(context, Math.max(0, attr.min), Math.min(attr.max, data.length - 1), 1);
 
        if (out) {
            me.trimByRange(context, out, viewMin, viewMax);
            context.majorTicks = out;
        }
    },
 
    /**
     * @method snapEnds
     * @inheritdoc
     */
    snapEnds: function(context, min, max, estStepSize) {
        var data = context.data,
            steps;
        
        estStepSize = Math.ceil(estStepSize);
        steps = Math.floor((max - min) / estStepSize);
 
        return {
            min: min,
            max: max,
            from: min,
            to: steps * estStepSize + min,
            step: estStepSize,
            steps: steps,
            unit: 1,
            getLabel: function(currentStep) {
                return data[this.from + this.step * currentStep];
            },
            get: function(currentStep) {
                return this.from + this.step * currentStep;
            }
        };
    },
 
    /**
     * @method trimByRange
     * @inheritdoc
     */
    trimByRange: function(context, out, trimMin, trimMax) {
        var unit = out.unit,
            beginIdx = Math.ceil((trimMin - out.from) / unit) * unit,
            endIdx = Math.floor((trimMax - out.from) / unit) * unit,
            begin = Math.max(0, Math.ceil(beginIdx / out.step)),
            end = Math.min(out.steps, Math.floor(endIdx / out.step));
 
        if (end < out.steps) {
            out.to = end;
        }
 
        if (out.max > trimMax) {
            out.max = out.to;
        }
 
        if (out.from < trimMin && out.step > 0) {
            out.from = out.from + begin * out.step * unit;
 
            while (out.from < trimMin) {
                begin++;
                out.from += out.step * unit;
            }
        }
 
        if (out.min < trimMin) {
            out.min = out.from;
        }
 
        out.steps = end - begin;
    },
 
    getCoordFor: function(value, field, idx, items) {
        this.labels.push(value);
 
        return this.labels.length - 1;
    }
});