text: 'use strict'
  
  const objectUtils = require('../object')
  
  /**
   * Convert a BigNumber to a formatted string representation.
   *
   * Syntax:
   *
   *    format(value)
   *    format(value, options)
   *    format(value, precision)
   *    format(value, fn)
   *
   * Where:
   *
   *    {number} value   The value to be formatted
   *    {Object} options An object with formatting options. Available options:
   *                     {string} notation
   *                         Number notation. Choose from:
   *                         'fixed'          Always use regular number notation.
   *                                          For example '123.40' and '14000000'
   *                         'exponential'    Always use exponential notation.
   *                                          For example '1.234e+2' and '1.4e+7'
   *                         'auto' (default) Regular number notation for numbers
   *                                          having an absolute value between
   *                                          `lower` and `upper` bounds, and uses
   *                                          exponential notation elsewhere.
   *                                          Lower bound is included, upper bound
   *                                          is excluded.
   *                                          For example '123.4' and '1.4e7'.
   *                     {number} precision   A number between 0 and 16 to round
   *                                          the digits of the number.
   *                                          In case of notations 'exponential' and
   *                                          'auto', `precision` defines the total
   *                                          number of significant digits returned.
   *                                          In case of notation 'fixed',
   *                                          `precision` defines the number of
   *                                          significant digits after the decimal
   *                                          point.
   *                                          `precision` is undefined by default.
   *                     {number} lowerExp    Exponent determining the lower boundary
   *                                          for formatting a value with an exponent
   *                                          when `notation='auto`.
   *                                          Default value is `-3`.
   *                     {number} upperExp    Exponent determining the upper boundary
   *                                          for formatting a value with an exponent
   *                                          when `notation='auto`.
   *                                          Default value is `5`.
   *    {Function} fn    A custom formatting function. Can be used to override the
   *                     built-in notations. Function `fn` is called with `value` as
   *                     parameter and must return a string. Is useful for example to
   *                     format all values inside a matrix in a particular way.
   *
   * Examples:
   *
   *    format(6.4)                                        // '6.4'
   *    format(1240000)                                    // '1.24e6'
   *    format(1/3)                                        // '0.3333333333333333'
   *    format(1/3, 3)                                     // '0.333'
   *    format(21385, 2)                                   // '21000'
   *    format(12e8, {notation: 'fixed'})                  // returns '1200000000'
   *    format(2.3,    {notation: 'fixed', precision: 4})  // returns '2.3000'
   *    format(52.8,   {notation: 'exponential'})          // returns '5.28e+1'
   *    format(12400,  {notation: 'engineering'})          // returns '12.400e+3'
   *
   * @param {BigNumber} value
   * @param {Object | Function | number} [options]
   * @return {string} str The formatted value
   */
  exports.format = function (value, options) {
    if (typeof options === 'function') {
      // handle format(value, fn)
      return options(value)
    }
  
    // handle special cases
    if (!value.isFinite()) {
      return value.isNaN() ? 'NaN' : (value.gt(0) ? 'Infinity' : '-Infinity')
    }
  
    // default values for options
    let notation = 'auto'
    let precision
  
    if (options !== undefined) {
      // determine notation from options
      if (options.notation) {
        notation = options.notation
      }
  
      // determine precision from options
      if (typeof options === 'number') {
        precision = options
      } else if (options.precision) {
        precision = options.precision
      }
    }
  
    // handle the various notations
    switch (notation) {
      case 'fixed':
        return exports.toFixed(value, precision)
  
      case 'exponential':
        return exports.toExponential(value, precision)
  
      case 'auto':
        // TODO: clean up some day. Deprecated since: 2018-01-24
        // @deprecated upper and lower are replaced with upperExp and lowerExp since v4.0.0
        if (options && options.exponential && (options.exponential.lower !== undefined || options.exponential.upper !== undefined)) {
          const fixedOptions = objectUtils.map(options, function (x) { return x })
          fixedOptions.exponential = undefined
          if (options.exponential.lower !== undefined) {
            fixedOptions.lowerExp = Math.round(Math.log(options.exponential.lower) / Math.LN10)
          }
          if (options.exponential.upper !== undefined) {
            fixedOptions.upperExp = Math.round(Math.log(options.exponential.upper) / Math.LN10)
          }
  
          console.warn('Deprecation warning: Formatting options exponential.lower and exponential.upper ' +
              '(minimum and maximum value) ' +
              'are replaced with exponential.lowerExp and exponential.upperExp ' +
              '(minimum and maximum exponent) since version 4.0.0. ' +
              'Replace ' + JSON.stringify(options) + ' with ' + JSON.stringify(fixedOptions))
  
          return exports.format(value, fixedOptions)
        }
  
        // determine lower and upper bound for exponential notation.
        // TODO: implement support for upper and lower to be BigNumbers themselves
        const lowerExp = (options && options.lowerExp !== undefined) ? options.lowerExp : -3
        const upperExp = (options && options.upperExp !== undefined) ? options.upperExp : 5
  
        // handle special case zero
        if (value.isZero()) return '0'
  
        // determine whether or not to output exponential notation
        let str
        const exp = value.logarithm()
        if (exp.gte(lowerExp) && exp.lt(upperExp)) {
          // normal number notation
          str = value.toSignificantDigits(precision).toFixed()
        } else {
          // exponential notation
          str = exports.toExponential(value, precision)
        }
  
        // remove trailing zeros after the decimal point
        return str.replace(/((\.\d*?)(0+))($|e)/, function () {
          const digits = arguments[2]
          const e = arguments[4]
          return (digits !== '.') ? digits + e : e
        })
  
      default:
        throw new Error('Unknown notation "' + notation + '". ' +
            'Choose "auto", "exponential", or "fixed".')
    }
  }
  
  /**
   * Format a number in exponential notation. Like '1.23e+5', '2.3e+0', '3.500e-3'
   * @param {BigNumber} value
   * @param {number} [precision]  Number of digits in formatted output.
   *                              If not provided, the maximum available digits
   *                              is used.
   * @returns {string} str
   */
  exports.toExponential = function (value, precision) {
    if (precision !== undefined) {
      return value.toExponential(precision - 1) // Note the offset of one
    } else {
      return value.toExponential()
    }
  }
  
  /**
   * Format a number with fixed notation.
   * @param {BigNumber} value
   * @param {number} [precision=undefined] Optional number of decimals after the
   *                                       decimal point. Undefined by default.
   */
  exports.toFixed = function (value, precision) {
    return value.toFixed(precision)
  }
  