/**
 * @abstract
 * @class Ext.chart.axis.layout.Layout
 *
 * Interface used by Axis to process its data into a meaningful layout.
 */
Ext.define('Ext.chart.axis.layout.Layout', {
    mixins: {
        observable: 'Ext.mixin.Observable'
    },
    config: {
        /**
         * @cfg {Ext.chart.axis.Axis} axis The axis that the Layout is bound.
         */
        axis: null
    },
 
    constructor: function (config) {
        this.mixins.observable.constructor.call(this, config);
    },
 
    /**
     * Processes the data of the series bound to the axis.
     * @param {Ext.chart.series.Series} series The bound series.
     */
    processData: function (series) {
        var me = this,
            axis = me.getAxis(),
            direction = axis.getDirection(),
            boundSeries = axis.boundSeries,
            i, ln;
 
        if (series) {
            series['coordinate' + direction]();
        } else {
            for (= 0, ln = boundSeries.length; i < ln; i++) {
                boundSeries[i]['coordinate' + direction]()
            }
        }
    },
 
    /**
     * Calculates the position of major ticks for the axis.
     * @param {Object} context 
     */
    calculateMajorTicks: function (context) {
        var me = this,
            attr = context.attr,
            range = attr.max - attr.min,
            zoom = range / Math.max(1, attr.length) * (attr.visibleMax - attr.visibleMin),
            viewMin = attr.min + range * attr.visibleMin,
            viewMax = attr.min + range * attr.visibleMax,
            estStepSize = attr.estStepSize * zoom,
            out = me.snapEnds(context, attr.min, attr.max, estStepSize);
 
        if (out) {
            me.trimByRange(context, out, viewMin, viewMax);
            context.majorTicks = out;
        }
    },
 
    /**
     * Calculates the position of sub ticks for the axis.
     * @param {Object} context 
     */
    calculateMinorTicks: function (context) {
        if (this.snapMinorEnds) {
            context.minorTicks = this.snapMinorEnds(context);
        }
    },
 
    /**
     * Calculates the position of tick marks for the axis.
     * @param {Object} context 
     * @return {*}
     */
    calculateLayout: function (context) {
        var me = this,
            attr = context.attr;
 
        if (attr.length === 0) {
            return null;
        }
 
        if (attr.majorTicks) {
            me.calculateMajorTicks(context);
            if (attr.minorTicks) {
                me.calculateMinorTicks(context);
            }
        }
    },
 
    /**
     * @method
     * Snaps the data bound to the axis to meaningful tick marks.
     * @param {Object} context 
     * @param {Number} min 
     * @param {Number} max 
     * @param {Number} estStepSize 
     */
    snapEnds: Ext.emptyFn,
 
    /**
     * Trims the layout of the axis by the defined minimum and maximum.
     * @param {Object} context 
     * @param {Object} ticks Ticks object (e.g. major ticks) to be modified.
     * @param {Number} trimMin 
     * @param {Number} trimMax 
     */
    trimByRange: function (context, ticks, trimMin, trimMax) {
        var segmenter = context.segmenter,
            unit = ticks.unit,
            beginIdx = segmenter.diff(ticks.from, trimMin, unit),
            endIdx = segmenter.diff(ticks.from, trimMax, unit),
            begin = Math.max(0, Math.ceil(beginIdx / ticks.step)),
            end = Math.min(ticks.steps, Math.floor(endIdx / ticks.step));
 
        if (end < ticks.steps) {
            ticks.to = segmenter.add(ticks.from, end * ticks.step, unit);
        }
 
        if (ticks.max > trimMax) {
            ticks.max = ticks.to;
        }
 
        if (ticks.from < trimMin) {
            ticks.from = segmenter.add(ticks.from, begin * ticks.step, unit);
            while (ticks.from < trimMin) {
                begin++;
                ticks.from = segmenter.add(ticks.from, ticks.step, unit);
            }
        }
 
        if (ticks.min < trimMin) {
            ticks.min = ticks.from;
        }
 
        ticks.steps = end - begin;
    }
});