/**
 * @class Ext.Number
 *
 * A collection of useful static methods to deal with numbers
 * @singleton
 */
Ext.Number = (new function() { // jshint ignore:line
// @define Ext.lang.Number
// @define Ext.Number
// @require Ext
    var ExtNumber = this,
        isToFixedBroken = (0.9).toFixed() !== '1',
        math = Math,
        ClipDefault = {
            count: false,
            inclusive: false,
            wrap: true
        };
 
    // polyfill
    Number.MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER || -(math.pow(2, 53) - 1);
    Number.MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || math.pow(2, 53) - 1;
 
    Ext.apply(ExtNumber, {
        MIN_SAFE_INTEGER: Number.MIN_SAFE_INTEGER,
        MAX_SAFE_INTEGER: Number.MAX_SAFE_INTEGER,
        // No good way to allow "9." w/o allowing "." alone but we use isNaN to reject that
        floatRe: /^[-+]?(?:\d+|\d*\.\d*)(?:[Ee][+-]?\d+)?$/,
        intRe: /^[-+]?\d+(?:[Ee]\+?\d+)?$/,
 
        Clip: {
            DEFAULT: ClipDefault,
 
            COUNT: Ext.applyIf({
                count: true
            }, ClipDefault),
 
            INCLUSIVE: Ext.applyIf({
                inclusive: true
            }, ClipDefault),
 
            NOWRAP: Ext.applyIf({
                wrap: false
            }, ClipDefault)
        },
 
        /**
         * Strictly parses the given value and returns the value as a number or `null` if
         * the value is not a number or contains non-numeric pieces.
         * @param {String} value 
         * @return {Number} 
         * @since 6.5.1
         */
        parseFloat: function(value) {
            if (value === undefined) {
                value = null;
            }
 
            if (value !== null && typeof value !== 'number') {
                value = String(value);
                value = ExtNumber.floatRe.test(value) ? +value : null;
                
                if (isNaN(value)) {
                    value = null;
                }
            }
 
            return value;
        },
 
        /**
         * Strictly parses the given value and returns the value as a number or `null` if
         * the value is not an integer number or contains non-integer pieces.
         * @param {String} value 
         * @return {Number} 
         * @since 6.5.1
         */
        parseInt: function(value) {
            if (value === undefined) {
                value = null;
            }
 
            if (typeof value === 'number') {
                value = Math.floor(value);
            }
            else if (value !== null) {
                value = String(value);
                value = ExtNumber.intRe.test(value) ? +value : null;
            }
 
            return value;
        },
 
        binarySearch: function(array, value, begin, end) {
            var middle, midVal;
 
            if (begin === undefined) {
                begin = 0;
            }
            
            if (end === undefined) {
                end = array.length;
            }
 
            --end;
 
            while (begin <= end) {
                middle = (begin + end) >>> 1; // unsigned right shift = Math.floor(x/2)
                midVal = array[middle];
 
                if (value === midVal) {
                    return middle;
                }
                
                if (midVal < value) {
                    begin = middle + 1;
                }
                else {
                    end = middle - 1;
                }
            }
 
            return begin;
        },
 
        bisectTuples: function(array, value, index, begin, end) {
            var middle, midVal;
 
            if (begin === undefined) {
                begin = 0;
            }
            
            if (end === undefined) {
                end = array.length;
            }
 
            --end;
 
            while (begin <= end) {
                middle = (begin + end) >>> 1; // unsigned right shift = Math.floor(x/2)
                midVal = array[middle][index];
 
                if (value === midVal) {
                    return middle;
                }
                
                if (midVal < value) {
                    begin = middle + 1;
                }
                else {
                    end = middle - 1;
                }
            }
 
            return begin;
        },
 
        /**
         * Coerces a given index into a valid index given a `length`.
         *
         * Negative indexes are interpreted starting at the end of the collection. That is,
         * a value of -1 indicates the last item, or equivalent to `length - 1`.
         *
         * When handling methods that take "begin" and "end" arguments like most array or
         * string methods, this method can be used like so:
         *
         *      function foo (array, begin, end) {
         *          var range = Ext.Number.clipIndices(array.length, [begin, end]);
         *
         *          begin = range[0];
         *          end   = range[1];
         *
         *          // 0 <= begin <= end <= array.length
         *
         *          var length = end - begin;
         *      }
         *
         * For example:
         *
         *      +---+---+---+---+---+---+---+---+
         *      |   |   |   |   |   |   |   |   |  length = 8
         *      +---+---+---+---+---+---+---+---+
         *        0   1   2   3   4   5   6   7
         *       -8  -7  -6  -5  -4  -3  -2  -1
         *
         *      console.log(Ext.Number.clipIndices(8, [3, 10]); // logs "[3, 8]"
         *      console.log(Ext.Number.clipIndices(8, [-5]);    // logs "[3, 8]"
         *      console.log(Ext.Number.clipIndices(8, []);
         *      console.log(Ext.Number.clipIndices(8, []);
         *
         * @param {Number} length 
         * @param {Number[]} indices 
         * @param {Object} [options] An object with different option flags.
         * @param {Boolean} [options.count=false] The second number in `indices` is the
         * count not and an index.
         * @param {Boolean} [options.inclusive=false] The second number in `indices` is
         * "inclusive" meaning that the item should be considered in the range. Normally,
         * the second number is considered the first item outside the range or as an
         * "exclusive" bound.
         * @param {Boolean} [options.wrap=true] Wraps negative numbers backwards from the
         * end of the array. Passing `false` simply clips negative index values at 0.
         * @return {Number[]} The normalized `[begin, end]` array where `end` is now
         * exclusive such that `length = end - begin`. Both values are between 0 and the
         * given `length` and `end` will not be less-than `begin`.
         */
        clipIndices: function(length, indices, options) {
            var defaultValue = 0, // default value for "begin"
                wrap, begin, end, i;
 
            options = options || ClipDefault;
            wrap = options.wrap;
 
            indices = indices || [];
            
            for (= 0; i < 2; ++i) {
                // names are off on first pass but used this way so things make sense
                // following the loop..
                begin = end; // pick up and keep the result from the first loop
                end = indices[i];
                
                if (end == null) {
                    end = defaultValue;
                }
                else if (&& options.count) {
                    end += begin; // this is the length not "end" so convert to "end"
                    end = (end > length) ? length : end;
                }
                else {
                    if (wrap) {
                        end = (end < 0) ? (length + end) : end;
                    }
                    
                    if (&& options.inclusive) {
                        ++end;
                    }
                    
                    end = (end < 0) ? 0 : ((end > length) ? length : end);
                }
                
                defaultValue = length; // default value for "end"
            }
 
            // after loop:
            // 0 <= begin <= length  (calculated from indices[0])
            // 0 <= end <= length    (calculated from indices[1])
 
            indices[0] = begin;
            indices[1] = (end < begin) ? begin : end;
            
            return indices;
        },
 
        /**
         * Checks whether or not the passed number is within a desired range. If the number is
         * already within the range it is returned, otherwise the min or max value is returned
         * depending on which side of the range is exceeded. Note that this method returns the
         * constrained value but does not change the current number.
         * @param {Number} number The number to check
         * @param {Number} min The minimum number in the range
         * @param {Number} max The maximum number in the range
         * @return {Number} The constrained value if outside the range, otherwise the current value
         */
        constrain: function(number, min, max) {
            var x = parseFloat(number);
 
            // (x < Nan) || (x < undefined) == false
            // same for (x > NaN) || (x > undefined)
            // sadly this is not true of null - (1 > null)
            if (min === null) {
                min = number;
            }
 
            if (max === null) {
                max = number;
            }
 
            // Watch out for NaN in Chrome 18
            // V8bug: http://code.google.com/p/v8/issues/detail?id=2056
 
            // Operators are faster than Math.min/max. See http://jsperf.com/number-constrain
            return (< min) ? min : ((> max) ? max : x);
        },
 
        /**
         * Snaps the passed number between stopping points based upon a passed increment value.
         *
         * The difference between this and {@link #snapInRange} is that {@link #snapInRange} uses
         * the minValue when calculating snap points:
         *
         *     // Returns 56 - snap points are zero based
         *     r = Ext.Number.snap(56, 2, 55, 65);
         *
         *     // Returns 57 - snap points are based from minValue
         *     r = Ext.Number.snapInRange(56, 2, 55, 65);
         *
         * @param {Number} value The unsnapped value.
         * @param {Number} increment The increment by which the value must move.
         * @param {Number} minValue The minimum value to which the returned value must be
         * constrained. Overrides the increment.
         * @param {Number} maxValue The maximum value to which the returned value must be
         * constrained. Overrides the increment.
         * @return {Number} The value of the nearest snap target.
         */
        snap: function(value, increment, minValue, maxValue) {
            var m;
 
            // If no value passed, or minValue was passed and value is less than minValue
            // (anything < undefined is false)
            // Then use the minValue (or zero if the value was undefined)
            if (value === undefined || value < minValue) {
                return minValue || 0;
            }
 
            if (increment) {
                m = value % increment;
                
                if (!== 0) {
                    value -= m;
                    
                    if (* 2 >= increment) {
                        value += increment;
                    }
                    else if (* 2 < -increment) {
                        value -= increment;
                    }
                }
            }
            
            return ExtNumber.constrain(value, minValue, maxValue);
        },
 
        /**
         * Snaps the passed number between stopping points based upon a passed increment value.
         *
         * The difference between this and {@link #snap} is that {@link #snap} does not use
         * the minValue when calculating snap points:
         *
         *     // Returns 56 - snap points are zero based
         *     r = Ext.Number.snap(56, 2, 55, 65);
         *
         *     // Returns 57 - snap points are based from minValue
         *     r = Ext.Number.snapInRange(56, 2, 55, 65);
         *
         * @param {Number} value The unsnapped value.
         * @param {Number} increment The increment by which the value must move.
         * @param {Number} [minValue=0] The minimum value to which the returned value must be
         * constrained.
         * @param {Number} [maxValue=Infinity] The maximum value to which the returned value
         * must be constrained.
         * @return {Number} The value of the nearest snap target.
         */
        snapInRange: function(value, increment, minValue, maxValue) {
            var tween;
 
            // default minValue to zero
            minValue = (minValue || 0);
 
            // If value is undefined, or less than minValue, use minValue
            if (value === undefined || value < minValue) {
                return minValue;
            }
 
            // Calculate how many snap points from the minValue the passed value is.
            if (increment && (tween = ((value - minValue) % increment))) {
                value -= tween;
                tween *= 2;
                
                if (tween >= increment) {
                    value += increment;
                }
            }
 
            // If constraining within a maximum, ensure the maximum is on a snap point
            if (maxValue !== undefined) {
                if (value > (maxValue = ExtNumber.snapInRange(maxValue, increment, minValue))) {
                    value = maxValue;
                }
            }
 
            return value;
        },
 
        /**
         * Round a number to the nearest interval.
         * @param {Number} value The value to round.
         * @param {Number} interval The interval to round to.
         * @return {Number} The rounded value.
         *
         * @since 6.2.0
         */
        roundToNearest: function(value, interval) {
            interval = interval || 1;
            
            return interval * math.round(value / interval);
        },
 
        /**
         * Rounds a number to the specified precision.
         * @param value
         * @param precision
         * @return {number} 
         */
        roundToPrecision: function(value, precision) {
            var factor = math.pow(10, precision || 1);
 
            return math.round(value * factor) / factor;
        },
 
        /**
         * Truncates a number to the specified precision,
         * without rounding.
         * @param value
         * @param precision
         * @return {number} 
         * @since 6.5.1
         */
        truncateToPrecision: function(value, precision) {
            var factor = math.pow(10, precision || 1);
 
            return parseInt(value * factor, 10) / factor;
        },
 
        /**
         * Returns the sign of the given number. See also MDN for Math.sign documentation
         * for the standard method this method emulates.
         * @param {Number} x The number.
         * @return {Number} The sign of the number `x`, indicating whether the number is
         * positive (1), negative (-1) or zero (0).
         * @method sign
         */
        sign: math.sign || function(x) {
            x = +x; // force to a Number
 
            if (=== 0 || isNaN(x)) {
                return x;
            }
 
            return (> 0) ? 1 : -1;
        },
 
        /**
         * Returns the base 10 logarithm of a number.
         * This will use Math.log10, if available (ES6).
         * @param {Number} x The number.
         * @return {Number} Base 10 logarithm of the number 'x'.
         * @method log10
         */
        log10: math.log10 || function(x) {
            return math.log(x) * math.LOG10E;
        },
 
        /**
         * Determines if two numbers `n1` and `n2` are equal within a given
         * margin of precision `epsilon`.
         * @param {Number} n1 First number.
         * @param {Number} n2 Second number.
         * @param {Number} epsilon Margin of precision.
         * @return {Boolean} `true`, if numbers are equal. `false` otherwise.
         */
        isEqual: function(n1, n2, epsilon) {
            //<debug>
            /* eslint-disable-next-line max-len */
            if (!(typeof n1 === 'number' && typeof n2 === 'number' && typeof epsilon === 'number')) {
                Ext.raise("All parameters should be valid numbers.");
            }
            //</debug>
            
            return math.abs(n1 - n2) < epsilon;
        },
 
        /**
         * Determines if the value passed is a number and also finite.
         * This a Polyfill version of Number.isFinite(),differently than
         * the isFinite() function, this method doesn't convert the parameter to a number.
         * @param {Number} value Number to be tested.
         * @return {Boolean} `true`, if the parameter is a number and finite, `false` otherwise.
         * @since 6.2
         */
        isFinite: Number.isFinite || function(value) {
            return typeof value === 'number' && isFinite(value);
        },
 
        isInteger: Number.isInteger || function(value) {
            // Add zero get a valid result in a special case where the value is a number string.
            // E.g. '10' + 0 is '100'.
            return ~~(value + 0) === value;
        },
 
        /**
         * @method
         * Formats a number using fixed-point notation
         * @param {Number} value The number to format
         * @param {Number} precision The number of digits to show after the decimal point
         */
        toFixed: isToFixedBroken
            ? function(value, precision) {
                var pow;
                
                precision = precision || 0;
                pow = math.pow(10, precision);
                
                return (math.round(value * pow) / pow).toFixed(precision);
            }
            : function(value, precision) {
                return value.toFixed(precision);
            },
 
        /**
         * Validate that a value is numeric and convert it to a number if necessary.
         * Returns the specified default value if it is not.
         *
         *      Ext.Number.from('1.23', 1); // returns 1.23
         *      Ext.Number.from('abc', 1); // returns 1
         *
         * @param {Object} value 
         * @param {Number} defaultValue The value to return if the original value is non-numeric
         * @return {Number} value, if numeric, defaultValue otherwise
         */
        from: function(value, defaultValue) {
            if (isFinite(value)) {
                value = parseFloat(value);
            }
 
            return !isNaN(value) ? value : defaultValue;
        },
 
        /**
         * Returns a random integer between the specified range (inclusive)
         * @param {Number} from Lowest value to return.
         * @param {Number} to Highest value to return.
         * @return {Number} A random integer within the specified range.
         */
        randomInt: function(from, to) {
            return math.floor(math.random() * (to - from + 1) + from);
        },
 
        /**
         * Corrects floating point numbers that overflow to a non-precise
         * value because of their floating nature, for example `0.1 + 0.2`
         * @param {Number} n The number
         * @return {Number} The correctly rounded number
         */
        correctFloat: function(n) {
            // This is to correct the type of errors where 2 floats end with
            // a long string of decimals, eg 0.1 + 0.2. When they overflow in this
            // manner, they usually go to 15-16 decimals, so we cut it off at 14.
            return parseFloat(n.toPrecision(14));
        }
    });
 
    /**
     * @deprecated 4.0.0 Please use {@link Ext.Number#from} instead.
     * @member Ext
     * @method num
     * @inheritdoc Ext.Number#from
     */
    Ext.num = function() {
        return ExtNumber.from.apply(this, arguments);
    };
}());