/**
 * @private
 */
Ext.define('Ext.chart.Util', {
    singleton: true,
 
    /**
     * @private
     * Given a `range` array of two (min/max) numbers and an arbitrary array of numbers
     * (`data`), updates the given `range`, if the range of `data` exceeds it.
     * Typically, one would start with the `[NaN, NaN]` range array instance, call the
     * method on multiple datasets with that range instance, then validate it with
     * {@link #validateRange}.
     * @param {Number[]} range
     * @param {Number[]} data
     */
    expandRange: function (range, data) {
        var length = data.length,
            min = range[0],
            max = range[1],
            i, value;
 
        for (= 0; i < length; i++) {
            value = data[i];
            // `null` is a "finite" number in JavaScript 
            // and greater than any negative number. 
            if (value == null || !isFinite(value)) {
                continue;
            }
            if (value < min || !isFinite(min)) {
                min = value;
            }
            if (value > max || !isFinite(max)) {
                max = value;
            }
        }
 
        range[0] = min;
        range[1] = max;
    },
 
    defaultRange: [0, 1],
 
    /**
     * @private
     * Makes sure the range exists, is not zero, and its min/max values are finite numbers.
     * If this is not the case, the values from the provided `defaultRange`
     * are used.
     *
     * The range to validate. Never modified.
     * @param {Number[]} range
     * The default range to use, if the given range is not a valid data structure,
     * if both values are infinities, or if both values are the same and dangerously
     * close to either infinity (which makes expansion of the range by the value of
     * `padding` impossible).
     * If only a single value is infinity, the other value will be derived
     * from the finite value by incrementing/decrementing it by the span
     * of the default range towards the infinity.
     * For example, if the `defaultRange` is `[0, 1]`, we have:
     *
     *     [5, Infinity]   --> [5, 6]
     *     [3, -Infinity]  --> [2, 3]
     *     [-Infinity, -5] --> [-6, -5]
     *     [-3, -Infinity] --> [-4, -3]
     *
     * @param {Number[]} [defaultRange=[0, 1]]
     * A non-negative padding to use in case of identical min/max.
     * Note that the range span is not guaranteed to be `padding * 2` in this case,
     * if min/max are close to MIN_SAFE_INTEGER/MAX_SAFE_INTEGER.
     * @param {Number} [padding=0.5]
     * @return {Number[]}
     */
    validateRange: function (range, defaultRange, padding) {
        defaultRange = defaultRange || this.defaultRange.slice();
        if (!(padding === 0 || padding > 0)) {
            padding = 0.5
        }
 
        if (!range || range.length !== 2) {
            return defaultRange;
        }
 
        range = [range[0], range[1]];
 
        if (!range[0]) {
            range[0] = 0;
        }
        if (!range[1]) {
            range[1] = 0;
        }
 
        if (padding && range[0] === range[1]) {
            range = [
                range[0] - padding,
                range[0] + padding
            ];
            // In case the range values are at Infinity, the expansion above by the value 
            // of 'padding' won't do us much good, so we still have to fall back to the 
            // 'defaultRange'. 
            if (range[0] === range[1]) {
                return defaultRange;
            }
        }
        // Same sign infinities are ruled out at this point. 
 
        var isFin0 = isFinite(range[0]);
        var isFin1 = isFinite(range[1]);
 
        if (!isFin0 && !isFin1) {
            return defaultRange;
        }
        // Different sign infinities are ruled out at this point. 
 
        if (isFin0 && !isFin1) {
            range[1] = range[0] +
                Ext.Number.sign(range[1]) * (defaultRange[1] - defaultRange[0]);
        } else if (isFin1 && !isFin0) {
            range[0] = range[1] +
                Ext.Number.sign(range[0]) * (defaultRange[1] - defaultRange[0]);
        }
        // All infinities are ruled out at this point. 
 
        return [
            Math.min(range[0], range[1]),
            Math.max(range[0], range[1])
        ];
    },
 
    applyAnimation: function (animation, oldAnimation) {
        if (!animation) {
            animation = {
                duration: 0
            };
        } else if (animation === true) {
            animation = {
                easing: 'easeInOut',
                duration: 500
            };
        }
        return oldAnimation ? Ext.apply({}, animation, oldAnimation) : animation;
    }
 
});