// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of intl;

/// The function that we pass internally to NumberFormat to get
/// the appropriate pattern (e.g. currency)
typedef String _PatternGetter(NumberSymbols symbols);

/// Provides the ability to format a number in a locale-specific way. The
/// format is specified as a pattern using a subset of the ICU formatting
/// patterns.
///
/// - `0` A single digit
/// - `#` A single digit, omitted if the value is zero
/// - `.` Decimal separator
/// - `-` Minus sign
/// - `,` Grouping separator
/// - `E` Separates mantissa and expontent
/// - `+` - Before an exponent, to say it should be prefixed with a plus sign.
/// - `%` - In prefix or suffix, multiply by 100 and show as percentage
/// - `‰ (\u2030)` In prefix or suffix, multiply by 1000 and show as per mille
/// - `¤ (\u00A4)` Currency sign, replaced by currency name
/// - `'` Used to quote special characters
/// - `;` Used to separate the positive and negative patterns (if both present)
///
/// For example,
///       var f = new NumberFormat("###.0#", "en_US");
///       print(f.format(12.345));
///       ==> 12.34
/// If the locale is not specified, it will default to the current locale. If
/// the format is not specified it will print in a basic format with at least
/// one integer digit and three fraction digits.
///
/// There are also standard patterns available via the special constructors.
/// e.g.
///       var percent = new NumberFormat.percentFormat("ar");
///       var eurosInUSFormat = new NumberFormat.currency(locale: "en_US",
///           symbol: "€");
/// There are four such constructors: decimalFormat, percentFormat,
/// scientificFormat and currencyFormat. However, at the moment,
/// scientificFormat prints only as equivalent to "#E0" and does not take
/// into account significant digits. The currencyFormat will default to the
/// three-letter name of the currency if no explicit name/symbol is provided.
class NumberFormat {
  /// Variables to determine how number printing behaves.
  // TODO(alanknight): If these remain as variables and are set based on the
  // pattern, can we make them final?
  String _negativePrefix = '-';
  String _positivePrefix = '';
  String _negativeSuffix = '';
  String _positiveSuffix = '';

  /// How many numbers in a group when using punctuation to group digits in
  /// large numbers. e.g. in en_US: "1,000,000" has a grouping size of 3 digits
  /// between commas.
  int _groupingSize = 3;

  /// In some formats the last grouping size may be different than previous
  /// ones, e.g. Hindi.
  int _finalGroupingSize = 3;

  /// Set to true if the format has explicitly set the grouping size.
  bool _groupingSizeSetExplicitly = false;
  bool _decimalSeparatorAlwaysShown = false;
  bool _useSignForPositiveExponent = false;
  bool _useExponentialNotation = false;

  /// Explicitly store if we are a currency format, and so should use the
  /// appropriate number of decimal digits for a currency.
  // TODO(alanknight): Handle currency formats which are specified in a raw
  /// pattern, not using one of the currency constructors.
  bool _isForCurrency = false;

  int maximumIntegerDigits = 40;
  int minimumIntegerDigits = 1;
  int maximumFractionDigits = 3;
  int minimumFractionDigits = 0;
  int minimumExponentDigits = 0;
  int _significantDigits = 0;

  ///  How many significant digits should we print.
  ///
  ///  Note that if significantDigitsInUse is the default false, this
  ///  will be ignored.
  int get significantDigits => _significantDigits;
  set significantDigits(int x) {
    _significantDigits = x;
    significantDigitsInUse = true;
  }

  bool significantDigitsInUse = false;

  /// For percent and permille, what are we multiplying by in order to
  /// get the printed value, e.g. 100 for percent.
  int get _multiplier => _internalMultiplier;
  set _multiplier(int x) {
    _internalMultiplier = x;
    _multiplierDigits = (log(_multiplier) / LN10).round();
  }

  int _internalMultiplier = 1;

  /// How many digits are there in the [_multiplier].
  int _multiplierDigits = 0;

  /// Stores the pattern used to create this format. This isn't used, but
  /// is helpful in debugging.
  String _pattern;

  /// The locale in which we print numbers.
  final String _locale;

  /// Caches the symbols used for our locale.
  NumberSymbols _symbols;

  /// The name of the currency to print, in ISO 4217 form.
  String currencyName;

  /// The symbol to be used when formatting this as currency.
  ///
  /// For example, "$", "US$", or "€".
  String _currencySymbol;

  /// The symbol to be used when formatting this as currency.
  ///
  /// For example, "$", "US$", or "€".
  String get currencySymbol => _currencySymbol ?? currencyName;

  /// The number of decimal places to use when formatting.
  ///
  /// If this is not explicitly specified in the constructor, then for
  /// currencies we use the default value for the currency if the name is given,
  /// otherwise we use the value from the pattern for the locale.
  ///
  /// So, for example,
  ///      new NumberFormat.currency(name: 'USD', decimalDigits: 7)
  /// will format with 7 decimal digits, because that's what we asked for. But
  ///       new NumberFormat.currency(locale: 'en_US', name: 'JPY')
  /// will format with zero, because that's the default for JPY, and the
  /// currency's default takes priority over the locale's default.
  ///       new NumberFormat.currency(locale: 'en_US')
  /// will format with two, which is the default for that locale.
  ///
  int get decimalDigits => _decimalDigits;

  int _decimalDigits;

  /// For currencies, the default number of decimal places to use in
  /// formatting. Defaults to two for non-currencies or currencies where it's
  /// not specified.
  int get _defaultDecimalDigits =>
      currencyFractionDigits[currencyName.toUpperCase()] ??
      currencyFractionDigits['DEFAULT'];

  /// If we have a currencyName, use the decimal digits for that currency,
  /// unless we've explicitly specified some other number.
  bool get _overridesDecimalDigits => decimalDigits != null || _isForCurrency;

  /// Transient internal state in which to build up the result of the format
  /// operation. We can have this be just an instance variable because Dart is
  /// single-threaded and unless we do an asynchronous operation in the process
  /// of formatting then there will only ever be one number being formatted
  /// at a time. In languages with threads we'd need to pass this on the stack.
  final StringBuffer _buffer = new StringBuffer();

  /// Create a number format that prints using [newPattern] as it applies in
  /// [locale].
  factory NumberFormat([String newPattern, String locale]) =>
      new NumberFormat._forPattern(locale, (x) => newPattern);

  /// Create a number format that prints as DECIMAL_PATTERN.
  NumberFormat.decimalPattern([String locale])
      : this._forPattern(locale, (x) => x.DECIMAL_PATTERN);

  /// Create a number format that prints as PERCENT_PATTERN.
  NumberFormat.percentPattern([String locale])
      : this._forPattern(locale, (x) => x.PERCENT_PATTERN);

  /// Create a number format that prints as SCIENTIFIC_PATTERN.
  NumberFormat.scientificPattern([String locale])
      : this._forPattern(locale, (x) => x.SCIENTIFIC_PATTERN);

  /// A regular expression to validate currency names are exactly three
  /// alphabetic characters.
  static final _checkCurrencyName = new RegExp(r'^[a-zA-Z]{3}$');

  /// Create a number format that prints as CURRENCY_PATTERN. (Deprecated:
  /// prefer NumberFormat.currency)
  ///
  /// If provided,
  /// use [nameOrSymbol] in place of the default currency name. e.g.
  ///        var eurosInCurrentLocale = new NumberFormat
  ///            .currencyPattern(Intl.defaultLocale, "€");
  @Deprecated("Use NumberFormat.currency")
  factory NumberFormat.currencyPattern(
      [String locale, String currencyNameOrSymbol]) {
    // If it looks like an iso4217 name, pass as name, otherwise as symbol.
    if (currencyNameOrSymbol != null &&
        _checkCurrencyName.hasMatch(currencyNameOrSymbol)) {
      return new NumberFormat.currency(
          locale: locale, name: currencyNameOrSymbol);
    } else {
      return new NumberFormat.currency(
          locale: locale, symbol: currencyNameOrSymbol);
    }
  }

  /// Create a [NumberFormat] that formats using the locale's CURRENCY_PATTERN.
  ///
  /// If [locale] is not specified, it will use the current default locale.
  ///
  /// If [name] is specified, the currency with that ISO 4217 name will be used.
  /// Otherwise we will use the default currency name for the current locale. If
  /// no [symbol] is specified, we will use the currency name in the formatted
  /// result. e.g.
  ///      var f = new NumberFormat.currency(locale: 'en_US', name: 'EUR')
  /// will format currency like "EUR1.23". If we did not specify the name, it
  /// would format like "USD1.23".
  ///
  /// If [symbol] is used, then that symbol will be used in formatting instead
  /// of the name. e.g.
  ///      var eurosInCurrentLocale = new NumberFormat.currency(symbol: "€");
  /// will format like "€1.23". Otherwise it will use the currency name.
  /// If this is not explicitly specified in the constructor, then for
  /// currencies we use the default value for the currency if the name is given,
  ///  otherwise we use the value from the pattern for the locale.
  ///
  /// If [decimalDigits] is specified, numbers will format with that many digits
  /// after the decimal place. If it's not, they will use the default for the
  /// currency in [name], and the default currency for [locale] if the currency
  /// name is not specified. e.g.
  ///       new NumberFormat.currency(name: 'USD', decimalDigits: 7)
  /// will format with 7 decimal digits, because that's what we asked for. But
  ///       new NumberFormat.currency(locale: 'en_US', name: 'JPY')
  /// will format with zero, because that's the default for JPY, and the
  /// currency's default takes priority over the locale's default.
  ///       new NumberFormat.currency(locale: 'en_US')
  /// will format with two, which is the default for that locale.
  // TODO(alanknight): Should we allow decimalDigits on other numbers.
  NumberFormat.currency(
      {String locale, String name, String symbol, int decimalDigits})
      : this._forPattern(locale, (x) => x.CURRENCY_PATTERN,
            name: name,
            currencySymbol: symbol,
            decimalDigits: decimalDigits,
            isForCurrency: true);

  /// Creates a [NumberFormat] for currencies, using the simple symbol for the
  /// currency if one is available (e.g. $, €), so it should only be used if the
  /// short currency symbol will be unambiguous.
  ///
  /// If [locale] is not specified, it will use the current default locale.
  ///
  /// If [name] is specified, the currency with that ISO 4217 name will be used.
  /// Otherwise we will use the default currency name for the current locale. We
  /// will assume that the symbol for this is well known in the locale and
  /// unambiguous. If you format CAD in an en_US locale using this format it
  /// will display as "$", which may be confusing to the user.
  ///
  /// If [decimalDigits] is specified, numbers will format with that many digits
  /// after the decimal place. If it's not, they will use the default for the
  /// currency in [name], and the default currency for [locale] if the currency
  /// name is not specified. e.g.
  ///       new NumberFormat.simpleCurrency(name: 'USD', decimalDigits: 7)
  /// will format with 7 decimal digits, because that's what we asked for. But
  ///       new NumberFormat.simpleCurrency(locale: 'en_US', name: 'JPY')
  /// will format with zero, because that's the default for JPY, and the
  /// currency's default takes priority over the locale's default.
  ///       new NumberFormat.simpleCurrency(locale: 'en_US')
  /// will format with two, which is the default for that locale.
  factory NumberFormat.simpleCurrency(
      {String locale, String name, int decimalDigits}) {
    return new NumberFormat._forPattern(locale, (x) => x.CURRENCY_PATTERN,
        name: name,
        computeCurrencySymbol: (format) =>
            _simpleCurrencySymbols[format.currencyName] ?? format.currencyName,
        decimalDigits: decimalDigits,
        isForCurrency: true);
  }

  /// Returns the simple currency symbol for given currency code, or
  /// [currencyCode] if no simple symbol is listed.
  ///
  /// The simple currency symbol is generally short, and the same or related to
  /// what is used in countries having the currency as an official symbol. It
  /// may be a symbol character, or may have letters, or both. It may be
  /// different according to the locale: for example, for an Arabic locale it
  /// may consist of Arabic letters, but for a French locale consist of Latin
  /// letters. It will not be unique: for example, "$" can appear for both USD
  /// and CAD.
  ///
  /// (The current implementation is the same for all locales, but this is
  /// temporary and callers shouldn't rely on it.)
  String simpleCurrencySymbol(String currencyCode) =>
      _simpleCurrencySymbols[currencyCode] ?? currencyCode;

  /// A map from currency names to the simple name/symbol.
  ///
  /// The simple currency symbol is generally short, and the same or related to
  /// what is used in countries having the currency as an official symbol. It
  /// may be a symbol character, or may have letters, or both. It may be
  /// different according to the locale: for example, for an Arabic locale it
  /// may consist of Arabic letters, but for a French locale consist of Latin
  /// letters. It will not be unique: for example, "$" can appear for both USD
  /// and CAD.
  ///
  /// (The current implementation is the same for all locales, but this is
  /// temporary and callers shouldn't rely on it.)
  static Map<String, String> _simpleCurrencySymbols = {
    "AFN": "Af.",
    "TOP": r"T$",
    "MGA": "Ar",
    "THB": "\u0e3f",
    "PAB": "B/.",
    "ETB": "Birr",
    "VEF": "Bs",
    "BOB": "Bs",
    "GHS": "GHS",
    "CRC": "\u20a1",
    "NIO": r"C$",
    "GMD": "GMD",
    "MKD": "din",
    "BHD": "din",
    "DZD": "din",
    "IQD": "din",
    "JOD": "din",
    "KWD": "din",
    "LYD": "din",
    "RSD": "din",
    "TND": "din",
    "AED": "dh",
    "MAD": "dh",
    "STD": "Db",
    "BSD": r"$",
    "FJD": r"$",
    "GYD": r"$",
    "KYD": r"$",
    "LRD": r"$",
    "SBD": r"$",
    "SRD": r"$",
    "AUD": r"$",
    "BBD": r"$",
    "BMD": r"$",
    "BND": r"$",
    "BZD": r"$",
    "CAD": r"$",
    "HKD": r"$",
    "JMD": r"$",
    "NAD": r"$",
    "NZD": r"$",
    "SGD": r"$",
    "TTD": r"$",
    "TWD": r"NT$",
    "USD": r"$",
    "XCD": r"$",
    "VND": "\u20ab",
    "AMD": "Dram",
    "CVE": "CVE",
    "EUR": "\u20ac",
    "AWG": "Afl.",
    "HUF": "Ft",
    "BIF": "FBu",
    "CDF": "FrCD",
    "CHF": "CHF",
    "DJF": "Fdj",
    "GNF": "FG",
    "RWF": "RF",
    "XOF": "CFA",
    "XPF": "FCFP",
    "KMF": "CF",
    "XAF": "FCFA",
    "HTG": "HTG",
    "PYG": "Gs",
    "UAH": "\u20b4",
    "PGK": "PGK",
    "LAK": "\u20ad",
    "CZK": "K\u010d",
    "SEK": "kr",
    "ISK": "kr",
    "DKK": "kr",
    "NOK": "kr",
    "HRK": "kn",
    "MWK": "MWK",
    "ZMK": "ZWK",
    "AOA": "Kz",
    "MMK": "K",
    "GEL": "GEL",
    "LVL": "Ls",
    "ALL": "Lek",
    "HNL": "L",
    "SLL": "SLL",
    "MDL": "MDL",
    "RON": "RON",
    "BGN": "lev",
    "SZL": "SZL",
    "TRY": "TL",
    "LTL": "Lt",
    "LSL": "LSL",
    "AZN": "man.",
    "BAM": "KM",
    "MZN": "MTn",
    "NGN": "\u20a6",
    "ERN": "Nfk",
    "BTN": "Nu.",
    "MRO": "MRO",
    "MOP": "MOP",
    "CUP": r"$",
    "CUC": r"$",
    "ARS": r"$",
    "CLF": "UF",
    "CLP": r"$",
    "COP": r"$",
    "DOP": r"$",
    "MXN": r"$",
    "PHP": "\u20b1",
    "UYU": r"$",
    "FKP": "£",
    "GIP": "£",
    "SHP": "£",
    "EGP": "E£",
    "LBP": "L£",
    "SDG": "SDG",
    "SSP": "SSP",
    "GBP": "£",
    "SYP": "£",
    "BWP": "P",
    "GTQ": "Q",
    "ZAR": "R",
    "BRL": r"R$",
    "OMR": "Rial",
    "QAR": "Rial",
    "YER": "Rial",
    "IRR": "Rial",
    "KHR": "Riel",
    "MYR": "RM",
    "SAR": "Rial",
    "BYR": "BYR",
    "RUB": "руб.",
    "MUR": "Rs",
    "SCR": "SCR",
    "LKR": "Rs",
    "NPR": "Rs",
    "INR": "\u20b9",
    "PKR": "Rs",
    "IDR": "Rp",
    "ILS": "\u20aa",
    "KES": "Ksh",
    "SOS": "SOS",
    "TZS": "TSh",
    "UGX": "UGX",
    "PEN": "S/.",
    "KGS": "KGS",
    "UZS": "so\u02bcm",
    "TJS": "Som",
    "BDT": "\u09f3",
    "WST": "WST",
    "KZT": "\u20b8",
    "MNT": "\u20ae",
    "VUV": "VUV",
    "KPW": "\u20a9",
    "KRW": "\u20a9",
    "JPY": "¥",
    "CNY": "¥",
    "PLN": "z\u0142",
    "MVR": "Rf",
    "NLG": "NAf",
    "ZMW": "ZK",
    "ANG": "ƒ",
    "TMT": "TMT",
  };

  /// Create a number format that prints in a pattern we get from
  /// the [getPattern] function using the locale [locale].
  ///
  /// The [currencySymbol] can either be specified directly, or we can pass a
  /// function [computeCurrencySymbol] that will compute it later, given other
  /// information, typically the verified locale.
  NumberFormat._forPattern(String locale, _PatternGetter getPattern,
      {String name,
      String currencySymbol,
      String computeCurrencySymbol(NumberFormat),
      int decimalDigits,
      bool isForCurrency: false})
      : _locale = Intl.verifiedLocale(locale, localeExists),
        _isForCurrency = isForCurrency {
    this._currencySymbol = currencySymbol;
    this._decimalDigits = decimalDigits;
    _symbols = numberFormatSymbols[_locale];
    currencyName = name ?? _symbols.DEF_CURRENCY_CODE;
    if (this._currencySymbol == null && computeCurrencySymbol != null) {
      this._currencySymbol = computeCurrencySymbol(this);
    }
    _setPattern(getPattern(_symbols));
  }

  /// A number format for compact representations, e.g. "1.2M" instead
  /// of "1,200,000".
  factory NumberFormat.compact({String locale}) {
    return new _CompactNumberFormat(
        locale: locale,
        formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_PATTERN);
  }

  /// A number format for "long" compact representations, e.g. "1.2 million"
  /// instead of of "1,200,000".
  factory NumberFormat.compactLong({String locale}) {
    return new _CompactNumberFormat(
        locale: locale,
        formatType: _CompactFormatType.COMPACT_DECIMAL_LONG_PATTERN);
  }

  /// A number format for compact currency representations, e.g. "$1.2M" instead
  /// of "$1,200,000", and which will automatically determine a currency symbol
  /// based on the currency name or the locale. See
  /// [NumberFormat.simpleCurrency].
  factory NumberFormat.compactSimpleCurrency({String locale, String name}) {
    return new _CompactNumberFormat(
        locale: locale,
        formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN,
        name: name,
        getPattern: (symbols) => symbols.CURRENCY_PATTERN,
        computeCurrencySymbol: (format) =>
            _simpleCurrencySymbols[format.currencyName] ?? format.currencyName,
        isForCurrency: true);
  }

  /// A number format for compact currency representations, e.g. "$1.2M" instead
  /// of "$1,200,000".
  factory NumberFormat.compactCurrency(
      {String locale, String name, String symbol, int decimalDigits}) {
    return new _CompactNumberFormat(
        locale: locale,
        formatType: _CompactFormatType.COMPACT_DECIMAL_SHORT_CURRENCY_PATTERN,
        name: name,
        decimalDigits: decimalDigits,
        isForCurrency: true);
  }

  /// Return the locale code in which we operate, e.g. 'en_US' or 'pt'.
  String get locale => _locale;

  /// Return true if the locale exists, or if it is null. The null case
  /// is interpreted to mean that we use the default locale.
  static bool localeExists(localeName) {
    if (localeName == null) return false;
    return numberFormatSymbols.containsKey(localeName);
  }

  /// Return the symbols which are used in our locale. Cache them to avoid
  /// repeated lookup.
  NumberSymbols get symbols => _symbols;

  /// Format [number] according to our pattern and return the formatted string.
  String format(number) {
    if (_isNaN(number)) return symbols.NAN;
    if (_isInfinite(number)) return "${_signPrefix(number)}${symbols.INFINITY}";

    _add(_signPrefix(number));
    _formatNumber(number.abs());
    _add(_signSuffix(number));

    var result = _buffer.toString();
    _buffer.clear();
    return result;
  }

  /// Parse the number represented by the string. If it's not
  /// parseable, throws a [FormatException].
  num parse(String text) => new _NumberParser(this, text).value;

  /// Format the main part of the number in the form dictated by the pattern.
  void _formatNumber(number) {
    if (_useExponentialNotation) {
      _formatExponential(number);
    } else {
      _formatFixed(number);
    }
  }

  /// Format the number in exponential notation.
  void _formatExponential(num number) {
    if (number == 0.0) {
      _formatFixed(number);
      _formatExponent(0);
      return;
    }

    var exponent = (log(number) / LN10).floor();
    var mantissa = number / pow(10.0, exponent);

    if (maximumIntegerDigits > 1 &&
        maximumIntegerDigits > minimumIntegerDigits) {
      // A repeating range is defined; adjust to it as follows.
      // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3;
      // -3,-4,-5=>-6, etc. This takes into account that the
      // exponent we have here is off by one from what we expect;
      // it is for the format 0.MMMMMx10^n.
      while ((exponent % maximumIntegerDigits) != 0) {
        mantissa *= 10;
        exponent--;
      }
    } else {
      // No repeating range is defined, use minimum integer digits.
      if (minimumIntegerDigits < 1) {
        exponent++;
        mantissa /= 10;
      } else {
        exponent -= minimumIntegerDigits - 1;
        mantissa *= pow(10, minimumIntegerDigits - 1);
      }
    }
    _formatFixed(mantissa);
    _formatExponent(exponent);
  }

  /// Format the exponent portion, e.g. in "1.3e-5" the "e-5".
  void _formatExponent(num exponent) {
    _add(symbols.EXP_SYMBOL);
    if (exponent < 0) {
      exponent = -exponent;
      _add(symbols.MINUS_SIGN);
    } else if (_useSignForPositiveExponent) {
      _add(symbols.PLUS_SIGN);
    }
    _pad(minimumExponentDigits, exponent.toString());
  }

  /// Used to test if we have exceeded Javascript integer limits.
  final _maxInt = pow(2, 52);

  /// Helpers to check numbers that don't conform to the [num] interface,
  /// e.g. Int64
  _isInfinite(number) => number is num ? number.isInfinite : false;
  _isNaN(number) => number is num ? number.isNaN : false;

  /// Helper to get the floor of a number which might not be num. This should
  /// only ever be called with an argument which is positive, or whose abs()
  ///  is negative. The second case is the maximum negative value on a
  ///  fixed-length integer. Since they are integers, they are also their own
  ///  floor.
  _floor(number) {
    if (number.isNegative && !(number.abs().isNegative)) {
      throw new ArgumentError(
          "Internal error: expected positive number, got $number");
    }
    return (number is num) ? number.floor() : number ~/ 1;
  }

  /// Helper to round a number which might not be num.
  _round(number) {
    if (number is num) {
      if (number.isInfinite) {
        return _maxInt;
      } else {
        return number.round();
      }
    } else if (number.remainder(1) == 0) {
      // Not a normal number, but int-like, e.g. Int64
      return number;
    } else {
      // TODO(alanknight): Do this more efficiently. If IntX  had floor and round we could avoid this.
      var basic = _floor(number);
      var fraction = (number - basic).toDouble().round();
      return fraction == 0 ? number : number + fraction;
    }
  }

  // Return the number of digits left of the decimal place in [number].
  static int numberOfIntegerDigits(number) {
    var simpleNumber = number.toDouble().abs();
    // It's unfortunate that we have to do this, but we get precision errors
    // that affect the result if we use logs, e.g. 1000000
    if (simpleNumber < 10) return 1;
    if (simpleNumber < 100) return 2;
    if (simpleNumber < 1000) return 3;
    if (simpleNumber < 10000) return 4;
    if (simpleNumber < 100000) return 5;
    if (simpleNumber < 1000000) return 6;
    if (simpleNumber < 10000000) return 7;
    if (simpleNumber < 100000000) return 8;
    if (simpleNumber < 1000000000) return 9;
    if (simpleNumber < 10000000000) return 10;
    if (simpleNumber < 100000000000) return 11;
    if (simpleNumber < 1000000000000) return 12;
    if (simpleNumber < 10000000000000) return 13;
    if (simpleNumber < 100000000000000) return 14;
    if (simpleNumber < 1000000000000000) return 15;
    if (simpleNumber < 10000000000000000) return 16;
    // We're past the point where being off by one on the number of digits
    // will affect the pattern, so now we can use logs.
    return max(1, (log(simpleNumber) / LN10).ceil());
  }

  int _fractionDigitsAfter(int remainingSignificantDigits) =>
      max(0, remainingSignificantDigits);

  /// Format the basic number portion, including the fractional digits.
  void _formatFixed(number) {
    var integerPart;
    int fractionPart;
    int extraIntegerDigits;
    var fractionDigits = maximumFractionDigits;

    var power = 0;
    var digitMultiplier;

    if (_isInfinite(number)) {
      integerPart = number.toInt();
      extraIntegerDigits = 0;
      fractionPart = 0;
    } else {
      // We have three possible pieces. First, the basic integer part. If this
      // is a percent or permille, the additional 2 or 3 digits. Finally the
      // fractional part.
      // We avoid multiplying the number because it might overflow if we have
      // a fixed-size integer type, so we extract each of the three as an
      // integer pieces.
      integerPart = _floor(number);
      var fraction = number - integerPart;

      /// If we have significant digits, recalculate the number of fraction
      /// digits based on that.
      if (significantDigitsInUse) {
        var integerLength = numberOfIntegerDigits(integerPart);
        var remainingSignificantDigits =
            significantDigits - _multiplierDigits - integerLength;
        fractionDigits = _fractionDigitsAfter(remainingSignificantDigits);
        if (remainingSignificantDigits < 0) {
          // We may have to round.
          var divideBy = pow(10, integerLength - significantDigits);
          integerPart = (integerPart / divideBy).round() * divideBy;
        }
      }
      power = pow(10, fractionDigits);
      digitMultiplier = power * _multiplier;

      // Multiply out to the number of decimal places and the percent, then
      // round. For fixed-size integer types this should always be zero, so
      // multiplying is OK.
      var remainingDigits = _round(fraction * digitMultiplier).toInt();
      // However, in rounding we may overflow into the main digits.
      if (remainingDigits >= digitMultiplier) {
        integerPart++;
        remainingDigits -= digitMultiplier;
      }
      // Separate out the extra integer parts from the fraction part.
      extraIntegerDigits = remainingDigits ~/ power;
      fractionPart = remainingDigits % power;
    }

    var integerDigits = _integerDigits(integerPart, extraIntegerDigits);
    var digitLength = integerDigits.length;
    var fractionPresent =
        fractionDigits > 0 && (minimumFractionDigits > 0 || fractionPart > 0);

    if (_hasIntegerDigits(integerDigits)) {
      _pad(minimumIntegerDigits - digitLength);
      for (var i = 0; i < digitLength; i++) {
        _addDigit(integerDigits.codeUnitAt(i));
        _group(digitLength, i);
      }
    } else if (!fractionPresent) {
      // If neither fraction nor integer part exists, just print zero.
      _addZero();
    }

    _decimalSeparator(fractionPresent);
    _formatFractionPart((fractionPart + power).toString());
  }

  /// Compute the raw integer digits which will then be printed with
  /// grouping and translated to localized digits.
  String _integerDigits(integerPart, extraIntegerDigits) {
    // If the int part is larger than 2^52 and we're on Javascript (so it's
    // really a float) it will lose precision, so pad out the rest of it
    // with zeros. Check for Javascript by seeing if an integer is double.
    var paddingDigits = '';
    if (1 is double && integerPart is num && integerPart > _maxInt) {
      var howManyDigitsTooBig = (log(integerPart) / LN10).ceil() - 16;
      var divisor = pow(10, howManyDigitsTooBig).round();
      paddingDigits = symbols.ZERO_DIGIT * howManyDigitsTooBig.toInt();
      integerPart = (integerPart / divisor).truncate();
    }

    var extra = extraIntegerDigits == 0 ? '' : extraIntegerDigits.toString();
    var intDigits = _mainIntegerDigits(integerPart);
    var paddedExtra =
        intDigits.isEmpty ? extra : extra.padLeft(_multiplierDigits, '0');
    return "${intDigits}${paddedExtra}${paddingDigits}";
  }

  /// The digit string of the integer part. This is the empty string if the
  /// integer part is zero and otherwise is the toString() of the integer
  /// part, stripping off any minus sign.
  String _mainIntegerDigits(integer) {
    if (integer == 0) return '';
    var digits = integer.toString();
    if (significantDigitsInUse && digits.length > significantDigits) {
      digits = digits.substring(0, significantDigits) +
          ''.padLeft(digits.length - significantDigits, '0');
    }
    // If we have a fixed-length int representation, it can have a negative
    // number whose negation is also negative, e.g. 2^-63 in 64-bit.
    // Remove the minus sign.
    return digits.startsWith('-') ? digits.substring(1) : digits;
  }

  /// Format the part after the decimal place in a fixed point number.
  void _formatFractionPart(String fractionPart) {
    var fractionCodes = fractionPart.codeUnits;
    var fractionLength = fractionPart.length;
    while (fractionCodes[fractionLength - 1] == _zero &&
        fractionLength > minimumFractionDigits + 1) {
      fractionLength--;
    }
    for (var i = 1; i < fractionLength; i++) {
      _addDigit(fractionCodes[i]);
    }
  }

  /// Print the decimal separator if appropriate.
  void _decimalSeparator(bool fractionPresent) {
    if (_decimalSeparatorAlwaysShown || fractionPresent) {
      _add(symbols.DECIMAL_SEP);
    }
  }

  /// Return true if we have a main integer part which is printable, either
  /// because we have digits left of the decimal point (this may include digits
  /// which have been moved left because of percent or permille formatting),
  /// or because the minimum number of printable digits is greater than 1.
  bool _hasIntegerDigits(String digits) =>
      digits.isNotEmpty || minimumIntegerDigits > 0;

  /// A group of methods that provide support for writing digits and other
  /// required characters into [_buffer] easily.
  void _add(String x) {
    _buffer.write(x);
  }

  void _addZero() {
    _buffer.write(symbols.ZERO_DIGIT);
  }

  void _addDigit(int x) {
    _buffer.writeCharCode(_localeZero + x - _zero);
  }

  /// Print padding up to [numberOfDigits] above what's included in [basic].
  void _pad(int numberOfDigits, [String basic = '']) {
    for (var i = 0; i < numberOfDigits - basic.length; i++) {
      _add(symbols.ZERO_DIGIT);
    }
    for (int i = 0; i < basic.codeUnits.length; i++) {
      _addDigit(basic.codeUnits[i]);
    }
  }

  /// We are printing the digits of the number from left to right. We may need
  /// to print a thousands separator or other grouping character as appropriate
  /// to the locale. So we find how many places we are from the end of the number
  /// by subtracting our current [position] from the [totalLength] and printing
  /// the separator character every [_groupingSize] digits, with the final
  /// grouping possibly being of a different size, [_finalGroupingSize].
  void _group(int totalLength, int position) {
    var distanceFromEnd = totalLength - position;
    if (distanceFromEnd <= 1 || _groupingSize <= 0) return;
    if (distanceFromEnd == _finalGroupingSize + 1) {
      _add(symbols.GROUP_SEP);
    } else if ((distanceFromEnd > _finalGroupingSize) &&
        (distanceFromEnd - _finalGroupingSize) % _groupingSize == 1) {
      _add(symbols.GROUP_SEP);
    }
  }

  /// Returns the code point for the character '0'.
  final _zero = '0'.codeUnits.first;

  /// Returns the code point for the locale's zero digit.
  // Note that there is a slight risk of a locale's zero digit not fitting
  // into a single code unit, but it seems very unlikely, and if it did,
  // there's a pretty good chance that our assumptions about being able to do
  // arithmetic on it would also be invalid.
  get _localeZero => symbols.ZERO_DIGIT.codeUnits.first;

  /// Returns the prefix for [x] based on whether it's positive or negative.
  /// In en_US this would be '' and '-' respectively.
  String _signPrefix(x) => x.isNegative ? _negativePrefix : _positivePrefix;

  /// Returns the suffix for [x] based on wether it's positive or negative.
  /// In en_US there are no suffixes for positive or negative.
  String _signSuffix(x) => x.isNegative ? _negativeSuffix : _positiveSuffix;

  void _setPattern(String newPattern) {
    if (newPattern == null) return;
    // Make spaces non-breaking
    _pattern = newPattern.replaceAll(' ', '\u00a0');
    var parser = new _NumberFormatParser(
        this, newPattern, currencySymbol, decimalDigits);
    parser.parse();
    if (_overridesDecimalDigits) {
      _decimalDigits ??= _defaultDecimalDigits;
      minimumFractionDigits = _decimalDigits;
      maximumFractionDigits = _decimalDigits;
    }
  }

  /// Explicitly turn off any grouping (e.g. by thousands) in this format.
  ///
  /// This is used in compact number formatting, where we
  /// omit the normal grouping. Best to know what you're doing if you call it.
  void turnOffGrouping() {
    _groupingSize = 0;
    _finalGroupingSize = 0;
  }

  String toString() => "NumberFormat($_locale, $_pattern)";
}

///  A one-time object for parsing a particular numeric string. One-time here
/// means an instance can only parse one string. This is implemented by
/// transforming from a locale-specific format to one that the system can parse,
/// then calls the system parsing methods on it.
class _NumberParser {
  /// The format for which we are parsing.
  final NumberFormat format;

  /// The text we are parsing.
  final String text;

  /// What we use to iterate over the input text.
  final _Stream input;

  /// The result of parsing [text] according to [format]. Automatically
  /// populated in the constructor.
  num value;

  /// The symbols used by our format.
  NumberSymbols get symbols => format.symbols;

  /// Where we accumulate the normalized representation of the number.
  final StringBuffer _normalized = new StringBuffer();

  /// Did we see something that indicates this is, or at least might be,
  /// a positive number.
  bool gotPositive = false;

  /// Did we see something that indicates this is, or at least might be,
  /// a negative number.
  bool gotNegative = false;

  /// Did we see the required positive suffix at the end. Should
  /// match [gotPositive].
  bool gotPositiveSuffix = false;

  /// Did we see the required negative suffix at the end. Should
  /// match [gotNegative].
  bool gotNegativeSuffix = false;

  /// Should we stop parsing before hitting the end of the string.
  bool done = false;

  /// Have we already skipped over any required prefixes.
  bool prefixesSkipped = false;

  /// If the number is percent or permill, what do we divide by at the end.
  int scale = 1;

  String get _positivePrefix => format._positivePrefix;
  String get _negativePrefix => format._negativePrefix;
  String get _positiveSuffix => format._positiveSuffix;
  String get _negativeSuffix => format._negativeSuffix;
  int get _zero => format._zero;
  int get _localeZero => format._localeZero;

  ///  Create a new [_NumberParser] on which we can call parse().
  _NumberParser(this.format, text)
      : this.text = text,
        this.input = new _Stream(text) {
    scale = format._internalMultiplier;
    value = parse();
  }

  ///  The strings we might replace with functions that return the replacement
  /// values. They are functions because we might need to check something
  /// in the context. Note that the ordering is important here. For example,
  /// [symbols.PERCENT] might be " %", and we must handle that before we
  /// look at an individual space.
  Map<String, Function> get replacements =>
      _replacements ??= _initializeReplacements();

  Map<String, Function> _replacements;

  Map<String, Function> _initializeReplacements() => {
        symbols.DECIMAL_SEP: () => '.',
        symbols.EXP_SYMBOL: () => 'E',
        symbols.GROUP_SEP: handleSpace,
        symbols.PERCENT: () {
          scale = _NumberFormatParser._PERCENT_SCALE;
          return '';
        },
        symbols.PERMILL: () {
          scale = _NumberFormatParser._PER_MILLE_SCALE;
          return '';
        },
        ' ': handleSpace,
        '\u00a0': handleSpace,
        '+': () => '+',
        '-': () => '-',
      };

  invalidFormat() =>
      throw new FormatException("Invalid number: ${input.contents}");

  /// Replace a space in the number with the normalized form. If space is not
  /// a significant character (normally grouping) then it's just invalid. If it
  /// is the grouping character, then it's only valid if it's followed by a
  /// digit. e.g. '$12 345.00'
  handleSpace() =>
      groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit ? '' : invalidFormat();

  /// Determine if a space is a valid character in the number. See
  /// [handleSpace].
  bool get groupingIsNotASpaceOrElseItIsSpaceFollowedByADigit {
    if (symbols.GROUP_SEP != '\u00a0' || symbols.GROUP_SEP != ' ') return true;
    var peeked = input.peek(symbols.GROUP_SEP.length + 1);
    return asDigit(peeked[peeked.length - 1]) != null;
  }

  /// Turn [char] into a number representing a digit, or null if it doesn't
  /// represent a digit in this locale.
  int asDigit(String char) {
    var charCode = char.codeUnitAt(0);
    var digitValue = charCode - _localeZero;
    if (digitValue >= 0 && digitValue < 10) {
      return digitValue;
    } else {
      return null;
    }
  }

  /// Check to see if the input begins with either the positive or negative
  /// prefixes. Set the [gotPositive] and [gotNegative] variables accordingly.
  void checkPrefixes({bool skip: false}) {
    bool checkPrefix(String prefix, skip) {
      var matched = prefix.isNotEmpty && input.startsWith(prefix);
      if (skip && matched) input.read(prefix.length);
      return matched;
    }

    // TODO(alanknight): There's a faint possibility of a bug here where
    // a positive prefix is followed by a negative prefix that's also a valid
    // part of the number, but that seems very unlikely.
    if (checkPrefix(_positivePrefix, skip)) gotPositive = true;
    if (checkPrefix(_negativePrefix, skip)) gotNegative = true;

    // Copied from Closure. It doesn't seem to be necessary to pass the test
    // suite, so I'm not sure it's really needed.
    if (gotPositive && gotNegative) {
      if (_positivePrefix.length > _negativePrefix.length) {
        gotNegative = false;
      } else if (_negativePrefix.length > _positivePrefix.length) {
        gotPositive = false;
      }
    }
  }

  /// If the rest of our input is either the positive or negative suffix,
  /// set [gotPositiveSuffix] or [gotNegativeSuffix] accordingly.
  void checkSuffixes() {
    var remainder = input.rest();
    if (remainder == _positiveSuffix) gotPositiveSuffix = true;
    if (remainder == _negativeSuffix) gotNegativeSuffix = true;
  }

  /// We've encountered a character that's not a digit. Go through our
  /// replacement rules looking for how to handle it. If we see something
  /// that's not a digit and doesn't have a replacement, then we're done
  /// and the number is probably invalid.
  void processNonDigit() {
    // It might just be a prefix that we haven't skipped. We don't want to
    // skip them initially because they might also be semantically meaningful,
    // e.g. leading %. So we allow them through the loop, but only once.
    var foundAnInterpretation = false;
    if (input.index == 0 && !prefixesSkipped) {
      prefixesSkipped = true;
      checkPrefixes(skip: true);
      foundAnInterpretation = true;
    }

    for (var key in replacements.keys) {
      if (input.startsWith(key)) {
        _normalized.write(replacements[key]());
        input.read(key.length);
        return;
      }
    }
    // We haven't found either of these things, this seems invalid.
    if (!foundAnInterpretation) {
      done = true;
    }
  }

  /// Parse [text] and return the resulting number. Throws [FormatException]
  /// if we can't parse it.
  num parse() {
    if (text == symbols.NAN) return double.NAN;
    if (text == "$_positivePrefix${symbols.INFINITY}$_positiveSuffix") {
      return double.INFINITY;
    }
    if (text == "$_negativePrefix${symbols.INFINITY}$_negativeSuffix") {
      return double.NEGATIVE_INFINITY;
    }

    checkPrefixes();
    var parsed = parseNumber(input);

    if (gotPositive && !gotPositiveSuffix) invalidNumber();
    if (gotNegative && !gotNegativeSuffix) invalidNumber();
    if (!input.atEnd()) invalidNumber();

    return parsed;
  }

  /// The number is invalid, throw a [FormatException].
  void invalidNumber() =>
      throw new FormatException("Invalid Number: ${input.contents}");

  /// Parse the number portion of the input, i.e. not any prefixes or suffixes,
  /// and assuming NaN and Infinity are already handled.
  num parseNumber(_Stream input) {
    if (gotNegative) {
      _normalized.write('-');
    }
    while (!done && !input.atEnd()) {
      int digit = asDigit(input.peek());
      if (digit != null) {
        _normalized.writeCharCode(_zero + digit);
        input.next();
      } else {
        processNonDigit();
      }
      checkSuffixes();
    }

    var normalizedText = _normalized.toString();
    num parsed = int.parse(normalizedText, onError: (message) => null);
    if (parsed == null) parsed = double.parse(normalizedText);
    return parsed / scale;
  }
}

/// Private class that parses the numeric formatting pattern and sets the
/// variables in [format] to appropriate values. Instances of this are
/// transient and store parsing state in instance variables, so can only be used
/// to parse a single pattern.
class _NumberFormatParser {
  /// The special characters in the pattern language. All others are treated
  /// as literals.
  static const _PATTERN_SEPARATOR = ';';
  static const _QUOTE = "'";
  static const _PATTERN_DIGIT = '#';
  static const _PATTERN_ZERO_DIGIT = '0';
  static const _PATTERN_GROUPING_SEPARATOR = ',';
  static const _PATTERN_DECIMAL_SEPARATOR = '.';
  static const _PATTERN_CURRENCY_SIGN = '\u00A4';
  static const _PATTERN_PER_MILLE = '\u2030';
  static const _PER_MILLE_SCALE = 1000;
  static const _PATTERN_PERCENT = '%';
  static const _PERCENT_SCALE = 100;
  static const _PATTERN_EXPONENT = 'E';
  static const _PATTERN_PLUS = '+';

  /// The format whose state we are setting.
  final NumberFormat format;

  /// The pattern we are parsing.
  final _StringIterator pattern;

  /// We can be passed a specific currency symbol, regardless of the locale.
  String currencySymbol;

  /// We can be given a specific number of decimal places, overriding the
  /// default.
  final int decimalDigits;

  /// Create a new [_NumberFormatParser] for a particular [NumberFormat] and
  /// [input] pattern.
  _NumberFormatParser(
      this.format, input, this.currencySymbol, this.decimalDigits)
      : pattern = _iterator(input) {
    pattern.moveNext();
  }

  /// The [NumberSymbols] for the locale in which our [format] prints.
  NumberSymbols get symbols => format.symbols;

  /// Parse the input pattern and set the values.
  void parse() {
    format._positivePrefix = _parseAffix();
    var trunk = _parseTrunk();
    format._positiveSuffix = _parseAffix();
    // If we have separate positive and negative patterns, now parse the
    // the negative version.
    if (pattern.current == _NumberFormatParser._PATTERN_SEPARATOR) {
      pattern.moveNext();
      format._negativePrefix = _parseAffix();
      // Skip over the negative trunk, verifying that it's identical to the
      // positive trunk.
      for (var each in _iterable(trunk)) {
        if (pattern.current != each && pattern.current != null) {
          throw new FormatException(
              "Positive and negative trunks must be the same");
        }
        pattern.moveNext();
      }
      format._negativeSuffix = _parseAffix();
    } else {
      // If no negative affix is specified, they share the same positive affix.
      format._negativePrefix = format._negativePrefix + format._positivePrefix;
      format._negativeSuffix = format._positiveSuffix + format._negativeSuffix;
    }
  }

  /// Variable used in parsing prefixes and suffixes to keep track of
  /// whether or not we are in a quoted region.
  bool inQuote = false;

  /// Parse a prefix or suffix and return the prefix/suffix string. Note that
  /// this also may modify the state of [format].
  String _parseAffix() {
    var affix = new StringBuffer();
    inQuote = false;
    while (parseCharacterAffix(affix) && pattern.moveNext());
    return affix.toString();
  }

  /// Parse an individual character as part of a prefix or suffix.  Return true
  /// if we should continue to look for more affix characters, and false if
  /// we have reached the end.
  bool parseCharacterAffix(StringBuffer affix) {
    var ch = pattern.current;
    if (ch == null) return false;
    if (ch == _QUOTE) {
      if (pattern.peek == _QUOTE) {
        pattern.moveNext();
        affix.write(_QUOTE); // 'don''t'
      } else {
        inQuote = !inQuote;
      }
      return true;
    }

    if (inQuote) {
      affix.write(ch);
    } else {
      switch (ch) {
        case _PATTERN_DIGIT:
        case _PATTERN_ZERO_DIGIT:
        case _PATTERN_GROUPING_SEPARATOR:
        case _PATTERN_DECIMAL_SEPARATOR:
        case _PATTERN_SEPARATOR:
          return false;
        case _PATTERN_CURRENCY_SIGN:
          // TODO(alanknight): Handle the local/global/portable currency signs
          affix.write(currencySymbol);
          break;
        case _PATTERN_PERCENT:
          if (format._multiplier != 1 && format._multiplier != _PERCENT_SCALE) {
            throw new FormatException('Too many percent/permill');
          }
          format._multiplier = _PERCENT_SCALE;
          affix.write(symbols.PERCENT);
          break;
        case _PATTERN_PER_MILLE:
          if (format._multiplier != 1 &&
              format._multiplier != _PER_MILLE_SCALE) {
            throw new FormatException('Too many percent/permill');
          }
          format._multiplier = _PER_MILLE_SCALE;
          affix.write(symbols.PERMILL);
          break;
        default:
          affix.write(ch);
      }
    }
    return true;
  }

  /// Variables used in [_parseTrunk] and [parseTrunkCharacter].
  var decimalPos = -1;
  var digitLeftCount = 0;
  var zeroDigitCount = 0;
  var digitRightCount = 0;
  var groupingCount = -1;

  /// Parse the "trunk" portion of the pattern, the piece that doesn't include
  /// positive or negative prefixes or suffixes.
  String _parseTrunk() {
    var loop = true;
    var trunk = new StringBuffer();
    while (pattern.current != null && loop) {
      loop = parseTrunkCharacter(trunk);
    }

    if (zeroDigitCount == 0 && digitLeftCount > 0 && decimalPos >= 0) {
      // Handle '###.###' and '###.' and '.###'
      // Handle '.###'
      var n = decimalPos == 0 ? 1 : decimalPos;
      digitRightCount = digitLeftCount - n;
      digitLeftCount = n - 1;
      zeroDigitCount = 1;
    }

    // Do syntax checking on the digits.
    if (decimalPos < 0 && digitRightCount > 0 ||
        decimalPos >= 0 &&
            (decimalPos < digitLeftCount ||
                decimalPos > digitLeftCount + zeroDigitCount) ||
        groupingCount == 0) {
      throw new FormatException('Malformed pattern "${pattern.input}"');
    }
    var totalDigits = digitLeftCount + zeroDigitCount + digitRightCount;

    format.maximumFractionDigits =
        decimalPos >= 0 ? totalDigits - decimalPos : 0;
    if (decimalPos >= 0) {
      format.minimumFractionDigits =
          digitLeftCount + zeroDigitCount - decimalPos;
      if (format.minimumFractionDigits < 0) {
        format.minimumFractionDigits = 0;
      }
    }

    // The effectiveDecimalPos is the position the decimal is at or would be at
    // if there is no decimal. Note that if decimalPos<0, then digitTotalCount
    // == digitLeftCount + zeroDigitCount.
    var effectiveDecimalPos = decimalPos >= 0 ? decimalPos : totalDigits;
    format.minimumIntegerDigits = effectiveDecimalPos - digitLeftCount;
    if (format._useExponentialNotation) {
      format.maximumIntegerDigits =
          digitLeftCount + format.minimumIntegerDigits;

      // In exponential display, we need to at least show something.
      if (format.maximumFractionDigits == 0 &&
          format.minimumIntegerDigits == 0) {
        format.minimumIntegerDigits = 1;
      }
    }

    format._finalGroupingSize = max(0, groupingCount);
    if (!format._groupingSizeSetExplicitly) {
      format._groupingSize = format._finalGroupingSize;
    }
    format._decimalSeparatorAlwaysShown =
        decimalPos == 0 || decimalPos == totalDigits;

    return trunk.toString();
  }

  /// Parse an individual character of the trunk. Return true if we should
  /// continue to look for additional trunk characters or false if we have
  /// reached the end.
  bool parseTrunkCharacter(trunk) {
    var ch = pattern.current;
    switch (ch) {
      case _PATTERN_DIGIT:
        if (zeroDigitCount > 0) {
          digitRightCount++;
        } else {
          digitLeftCount++;
        }
        if (groupingCount >= 0 && decimalPos < 0) {
          groupingCount++;
        }
        break;
      case _PATTERN_ZERO_DIGIT:
        if (digitRightCount > 0) {
          throw new FormatException(
              'Unexpected "0" in pattern "' + pattern.input + '"');
        }
        zeroDigitCount++;
        if (groupingCount >= 0 && decimalPos < 0) {
          groupingCount++;
        }
        break;
      case _PATTERN_GROUPING_SEPARATOR:
        if (groupingCount > 0) {
          format._groupingSizeSetExplicitly = true;
          format._groupingSize = groupingCount;
        }
        groupingCount = 0;
        break;
      case _PATTERN_DECIMAL_SEPARATOR:
        if (decimalPos >= 0) {
          throw new FormatException(
              'Multiple decimal separators in pattern "$pattern"');
        }
        decimalPos = digitLeftCount + zeroDigitCount + digitRightCount;
        break;
      case _PATTERN_EXPONENT:
        trunk.write(ch);
        if (format._useExponentialNotation) {
          throw new FormatException(
              'Multiple exponential symbols in pattern "$pattern"');
        }
        format._useExponentialNotation = true;
        format.minimumExponentDigits = 0;

        // exponent pattern can have a optional '+'.
        pattern.moveNext();
        var nextChar = pattern.current;
        if (nextChar == _PATTERN_PLUS) {
          trunk.write(pattern.current);
          pattern.moveNext();
          format._useSignForPositiveExponent = true;
        }

        // Use lookahead to parse out the exponential part
        // of the pattern, then jump into phase 2.
        while (pattern.current == _PATTERN_ZERO_DIGIT) {
          trunk.write(pattern.current);
          pattern.moveNext();
          format.minimumExponentDigits++;
        }

        if ((digitLeftCount + zeroDigitCount) < 1 ||
            format.minimumExponentDigits < 1) {
          throw new FormatException('Malformed exponential pattern "$pattern"');
        }
        return false;
      default:
        return false;
    }
    trunk.write(ch);
    pattern.moveNext();
    return true;
  }
}

/// Returns an [Iterable] on the string as a list of substrings.
Iterable _iterable(String s) => new _StringIterable(s);

/// Return an iterator on the string as a list of substrings.
Iterator<String> _iterator(String s) => new _StringIterator(s);

// TODO(nweiz): remove this when issue 3780 is fixed.
/// Provides an Iterable that wraps [_iterator] so it can be used in a `for`
/// loop.
class _StringIterable extends IterableBase<String> {
  final Iterator<String> iterator;

  _StringIterable(String s) : iterator = _iterator(s);
}

/// Provides an iterator over a string as a list of substrings, and also
/// gives us a lookahead of one via the [peek] method.
class _StringIterator implements Iterator<String> {
  final String input;
  int nextIndex = 0;
  String _current = null;

  _StringIterator(input) : input = _validate(input);

  String get current => _current;

  bool moveNext() {
    if (nextIndex >= input.length) {
      _current = null;
      return false;
    }
    _current = input[nextIndex++];
    return true;
  }

  String get peek => nextIndex >= input.length ? null : input[nextIndex];

  Iterator<String> get iterator => this;

  static String _validate(input) {
    if (input is! String) throw new ArgumentError(input);
    return input;
  }
}

/// Used primarily for currency formatting, this number-like class stores
/// millionths of a currency unit, typically as an Int64.
///
/// It supports no operations other than being used for Intl number formatting.
abstract class MicroMoney {
  factory MicroMoney(micros) => new _MicroMoney(micros);
}

/// Used primarily for currency formatting, this stores millionths of a
/// currency unit, typically as an Int64.
///
/// This private class provides the operations needed by the formatting code.
class _MicroMoney implements MicroMoney {
  var _micros;
  _MicroMoney(this._micros);
  static const _multiplier = 1000000;

  get _integerPart => _micros ~/ _multiplier;
  int get _fractionPart => (this - _integerPart)._micros.toInt().abs();

  bool get isNegative => _micros.isNegative;

  _MicroMoney abs() => isNegative ? new _MicroMoney(_micros.abs()) : this;

  // Note that if this is done in a general way there's a risk of integer
  // overflow on JS when multiplying out the [other] parameter, which may be
  // an Int64. In formatting we only ever subtract out our own integer part.
  _MicroMoney operator -(other) {
    if (other is _MicroMoney) return new _MicroMoney(_micros - other._micros);
    return new _MicroMoney(_micros - (other * _multiplier));
  }

  _MicroMoney operator +(other) {
    if (other is _MicroMoney) return new _MicroMoney(_micros + other._micros);
    return new _MicroMoney(_micros + (other * _multiplier));
  }

  _MicroMoney operator ~/(divisor) {
    if (divisor is! int) {
      throw new ArgumentError.value(
          divisor, 'divisor', '_MicroMoney ~/ only supports int arguments.');
    }
    return new _MicroMoney((_integerPart ~/ divisor) * _multiplier);
  }

  _MicroMoney operator *(other) {
    if (other is! int) {
      throw new ArgumentError.value(
          other, 'other', '_MicroMoney * only supports int arguments.');
    }
    return new _MicroMoney(
        (_integerPart * other) * _multiplier + (_fractionPart * other));
  }

  /// Note that this only really supports remainder from an int,
  /// not division by another MicroMoney
  _MicroMoney remainder(other) {
    if (other is! int) {
      throw new ArgumentError.value(
          other, 'other', '_MicroMoney.remainder only supports int arguments.');
    }
    return new _MicroMoney(_micros.remainder(other * _multiplier));
  }

  double toDouble() => _micros.toDouble() / _multiplier;

  int toInt() => _integerPart.toInt();

  String toString() {
    var beforeDecimal = _integerPart.toString();
    var decimalPart = '';
    var fractionPart = _fractionPart;
    if (fractionPart != 0) {
      decimalPart = '.' + fractionPart.toString();
    }
    return '$beforeDecimal$decimalPart';
  }
}
