# Source: core/converter/number-converter.js

    // Number and String formatting functions from Google Closure Library
    // http://code.google.com/closure/library/
    // /library/format/format.js

    // Copyright 2010 The Closure Library Authors. All Rights Reserved.
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
    // you may not use this file except in compliance with the License.
    // You may obtain a copy of the License at
    //
    //      http://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS-IS" BASIS,
    // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    // See the License for the specific language governing permissions and
    // limitations under the License.
    //
    // Copyright © 2011 Andrew Plummer
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the “Software”), to
    // deal in the Software without restriction, including without limitation the
    // rights to use, copy, modify, merge, publish, distribute, sub-license, and/or
    // sell copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    //
    // The above copyright notice, and every other copyright notice found in this
    // software, and all the attributions in every file, and this permission notice
    // shall be included in all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED
    //
    /**
     * @module montage/core/converter/number-converter
     * @requires montage/core/converter/converter
     */
    var Converter = require("./converter").Converter;
    var Validator = require("./converter").Validator;
    var isNumber = require("./converter").isNumber;
    var isDef = require("./converter").isDef;
    require('../shim/string');

    /**
     * Regular expression for detecting scaling units, such as K, M, G, etc. for
     * converting a string representation to a numeric value.  Also allow 'k' to be
     * aliased to 'K'.  These could be used for SI (powers of 1000) or Binary
     * (powers of 1024) conversions.  Also allow final 'B' to be interpreted as
     * byte-count, implicitly triggering binary conversion (e.g., '10.2MB').
     * @type {RegExp}
     * @memberof module:montage/core/converter#
     * @private
     */
    var SCALED_NUMERIC_RE_ = /^([\-]?\d+\.?\d*)([K,M,G,T,P,k,m,u,n]?)[B]?$/;

    /**
     * Ordered list of scaling prefixes in decreasing order.
     * @memberof module:montage/converter#
     * @type {Array}
     * @private
     */
        // kishore - changed prefix 'G' to 'B' to represent Billion
    var NUMERIC_SCALE_PREFIXES_ = [
        'P', 'T', 'B', 'M', 'K', '', 'm', 'u', 'n'
    ];

    /**
     * Scaling factors for conversion of numeric value to string.  SI conversion.
     * @memberof module:montage/converter#
     * @type {Object}
     * @private
     */
    var NUMERIC_SCALES_SI_ = exports.NUMERIC_SCALES_SI_ = {
        '': 1,
        'n': 1e-9,
        'u': 1e-6,
        'm': 1e-3,
        'k': 1e3,
        'K': 1e3,
        'M': 1e6,
        'B': 1e9,
        'T': 1e12,
        'P': 1e15
    };

    /**
     * Scaling factors for conversion of numeric value to string. Binary conversion.
     * @memberof module:montage/converter#
     * @type {Object}
     * @private
     */
    var NUMERIC_SCALES_BINARY_ = exports.NUMERIC_SCALES_BINARY_ = {
        '': 1,
        'n': Math.pow(1024, -3),
        'u': Math.pow(1024, -2),
        'm': 1.0 / 1024,
        'k': 1024,
        'K': 1024,
        'M': Math.pow(1024, 2),
        'G': Math.pow(1024, 3),
        'T': Math.pow(1024, 4),
        'P': Math.pow(1024, 5)
    };

    /**
     * Converts a numeric value to string, using specified conversion scales.
     * @memberof module:montage/converter#
     * @param {number} val Value to be converted.
     * @param {Object} conversion Dictionary of scaling factors.
     * @param {number} optDecimals The number of decimals to use.  Default is 2.
     * @param {string} optSuffix Optional suffix to append.
     * @returns {string} The human readable form of the byte size.
     * @private
     */
    var _numericValueToString = exports._numericValueToString = function (val, conversion, optDecimals, optSuffix, prefixes) {
        prefixes = prefixes || NUMERIC_SCALE_PREFIXES_;
        var origVal = val;
        var symbol = '';
        var scale = 1;
        if (val < 0) {
            val = -val;
        }
        for (var i = 0; i < prefixes.length; i++) {
            var unit = prefixes[i];
            scale = conversion[unit];
            if (val >= scale || (scale <= 1 && val > 0.1 * scale)) {
                // Treat values less than 1 differently, allowing 0.5 to be "0.5" rather
                // than "500m"
                symbol = unit;
                break;
            }
        }
        if (!symbol) {
            scale = 1;
        } else if (optSuffix) {
            symbol += optSuffix;
        }
        var ex = Math.pow(10, isDef(optDecimals) ? optDecimals : 2);
        return Math.round(origVal / scale * ex) / ex + symbol;
    };

    /**
     * Converts a string to numeric value, taking into account the units.
     * @memberof module:montage/converter#
     * @param {string} stringValue String to be converted to numeric value.
     * @param {Object} conversion Dictionary of conversion scales.
     * @returns {number} Numeric value for string.  If it cannot be converted, returns NaN.
     * @private
     */
    var _stringToNumericValue = function (stringValue, conversion) {
        var match = stringValue.match(SCALED_NUMERIC_RE_);
        if (!match) {
            return NaN;
        }
        return match[1] * conversion[match[2]];
    };

    /**
     * Checks whether string value containing scaling units (K, M, G, T, P, m, u,
     * n) can be converted to a number.
     * Where there is a decimal, there must be a digit to the left of the decimal
     * point.
     * Negative numbers are valid.
     * @example 0, 1, 1.0, 10.4K, 2.3M, -0.3P, 1.2m
     * @memberof module:montage/core/converter#
     * @function
     * @param {string} val String value to check.
     * @returns {boolean} true If the string could be converted to a numeric value.
     */
    var isConvertableScaledNumber = function (val) {
        return SCALED_NUMERIC_RE_.test(val);
    };

    /**
     * Converts a string to numeric value, taking into account the units.
     * If string ends in 'B', use binary conversion.
     * @memberof module:montage/core/converter#
     * @function
     * @param {string} stringValue String to be converted to numeric value.
     * @returns {number} Numeric value for string.
     */
    var stringToNumericValue = exports.stringToNumericValue = function (stringValue) {
        if (stringValue.endsWith('B')) {
            return _stringToNumericValue(
                stringValue, NUMERIC_SCALES_BINARY_);
        }
        return _stringToNumericValue(
            stringValue, NUMERIC_SCALES_SI_);
    };

    /**
     * Converts a numeric value to string representation. SI conversion.
     * @memberof module:montage/core/converter#
     * @function
     * @param {number} val Value to be converted.
     * @param {number} optDecimals The number of decimals to use. Defaults to 2.
     * @returns {string} String representation of number.
     */
    var numericValueToString = exports.numericValueToString = function (val, optDecimals) {
        return _numericValueToString(val, NUMERIC_SCALES_SI_, optDecimals);
    };

    /**
     * @class NumberValidator
     * @classdesc Validates that a string can be represented as a numeric value, and returns the numeric value.
     * @extends Validator
     */
    var NumberValidator = exports.NumberValidator = Validator.specialize( /** @lends NumberValidator# */ {

        /**
         * Indicates whether floating point values are allowed.
         * If `true` (the default) then the validator attempts to parse the string
         * as a float value.
         * If `false`, it attempts to parse the value as an integer.
         * @type {Property}
         * @default {boolean} true
         */
        allowFloat: {
            value: true
        },

        /**
         * @type {Property}
         * @default {boolean} true
         */
        allowNegative: {
            value: true
        },

        /**
         * Determines if the parameter `v` is a number or not.
         * @function
         * @param {string} v The value to validate as a number.
         * @returns {number} num An integer or float, if the value provided to the
         * function can parsed as a number;
         * otherwise returns an error.
         */
        validate: {
            value: function (v) {
                var num;
                v = v || '';
                v = v.replace(/,/g, '');

                if (isNumber(v)) {
                    num = v;
                } else {
                    num = (this.allowFloat === true ? parseFloat(v, 10) : parseInt(v, 10));
                }
                if (isNaN(num)) {
                    // error
                    return {message: 'Invalid Number'};
                } else {
                    return num;
                }
            }
        }

    });

    /**
     * @class NumberConverter
     * @classdesc Formats a number for easier readability.
     */
    var NumberConverter = exports.NumberConverter = Converter.specialize( /** @lends NumberConverter# */ {

        /**
         * Allow partial conversion
         * @type {Property}
         * @default {boolean} false
         */
        allowPartialConversion: {
            value: false
        },

        /**
         * @type {Function}
         * @default {attribute} NumberValidator Uses this object.
         */
        validator: {
            value: new NumberValidator()
        },

        /**
         * @type {Property}
         * @default {string} null
         */
            // valid fn values are:
        shorten: {
            value: null
        },

        /**
         * @type {Property}
         * @default {number} 2
         */
        decimals: {
            value: 2
        },

        /**
         * @type {Property}
         * @default {boolean} false
         */
        forceDecimals: {
            value: false
        },

        /**
         * @type {Property}
         * @default {number} null
         */
        round: {
            value: null
        },

        _reg: {
            value: /(\d+)(\d{3})/
        },

        /**
         * @type {Property}
         * @default {boolean} true
         */
        allowFloat: {
            value: true
        },

        /**
         * @type {Property}
         * @default {boolean} true
         */
        allowNegative: {
            value: true
        },

        // credit: sugar.js - https://github.com/andrewplummer/Sugar
        _makeReadable: {
            value: function (num, comma, period) {
                comma = comma || ',';
                period = period || '.';
                var split = num.toString().split('.');

                var numeric = split[0];
                while (this._reg.test(numeric)) {
                    numeric = numeric.replace(this._reg, '$1' + comma + '$2');
                }

                var afterDecimal = split.length > 1 ? split[1] : '';
                if (this.forceDecimals) {
                    while (afterDecimal.length < this.decimals) {
                        afterDecimal += '0';
                    }
                }

                var decimal = afterDecimal.length > 0 ? period + afterDecimal : '';
                return numeric + decimal;
            }
        },

        /**
         * @function
         * @param {number} value The value to convert.
         * @returns {string}
         */
        convert: {
            value: function (v) {
                if (this.shorten) {
                    // shorten the number to 10K, 100K, 1M etc
                    return numericValueToString(v, this.decimals);
                } else {
                    var num;
                    if (this.round) {
                        num = Number(Math.round(v)).toString();
                    } else {
                        var ex = Math.pow(10, this.decimals || 2);
                        var scale = 1;
                        num = Number(Math.round(v / scale * ex) / ex);
                    }
                    return this._makeReadable(num); //.toString();

                }
            }
        },

        /**
         * @function
         * @param {string} stringValue The string representation of a number.
         * @returns {number} The numeric value validated with to {@link
         * NumberConverter#validator}.
         * @throws {Error} if the return value of {@link
         * NumberConverter#validator#validate} is not a number
         * @see NumberConverter#validator
         * @see NumberConverter#allowFloat
         * @see NumberConverter#allowNegative
         */
        revert: {
            value: function (stringValue) {
                // use a Validator to validate first
                this.validator.allowFloat = this.allowFloat;
                this.validator.allowNegative = this.allowNegative;

                var result = this.validator.validate(stringValue);

                if (isNumber(result)) {
                    return result;
                } else {
                    // error object {code, message}
                    //return null;
                    throw new Error(result.message);
                }
                //return parseFloat(stringValue, 10) || null;
            }
        }

    });