/*
 * Copyright 2014 Adam Mackler
 *
 * 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.
 */

package org.bitcoinj.utils;

import com.google.common.base.Strings;
import org.bitcoinj.base.Coin;
import org.bitcoinj.utils.BtcAutoFormat.Style;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.AttributedCharacterIterator;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.FieldPosition;
import java.text.Format;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.math.RoundingMode.HALF_UP;
import static org.bitcoinj.base.internal.Preconditions.checkArgument;
import static org.bitcoinj.base.internal.Preconditions.checkState;
import static org.bitcoinj.utils.BtcAutoFormat.Style.CODE;
import static org.bitcoinj.utils.BtcAutoFormat.Style.SYMBOL;

/**
 * <p>Instances of this class format and parse locale-specific numerical
 * representations of Bitcoin monetary values.</p>
 *
 * <p>A primary goal of this class is to minimize the danger of
 * human-misreading of monetary values due to mis-counting the number
 * of zeros (or, more generally, of decimal places) in the number that
 * represents a Bitcoin monetary value.  Some of the features offered for doing this
 * are:</p>
 *
 * <ol>
 *   <li>automatic adjustment of denominational units in which a
 *       value is represented so as to lessen the number of adjacent zeros,
 *   <li>use of locale-specific decimal-separators to group digits in
 *       the integer portion of formatted numbers,
 *   <li>fine control over the number and  grouping of fractional decimal places, and
 *   <li>access to character information that allows for vertical
 *       alignment of tabular columns of formatted values.</ol>
 *
 * <h2>Basic Usage</h2>
 *
 * <p>Basic usage is very simple:</p>
 *
 * <ol>
 *   <li>Construct a new formatter object using one of the factory methods. 
 *   <li>Format a value by passing it as an argument to the
 *       {@link BtcFormat#format(Object)} method. 
 *   <li>Parse a value by passing a {@code String}-type
 *       representation of it to the {@link BtcFormat#parse(String)} method.</ol>
 *
 * <p>For example, depending on your locale, values might be formatted
 * and parsed as follows:</p>
 *
 * <blockquote><pre>
 * BtcFormat f = BtcFormat.getInstance();
 * String c = f.format(Coin.COIN);                <strong>// "BTC 1.00"</strong>
 * String k = f.format(Coin.COIN.multiply(1000)); <strong>// "BTC 1,000.00"</strong>
 * String m = f.format(Coin.COIN.divide(1000));   <strong>// "mBTC 1.00"</strong>
 * Coin all = f.parseObject("M฿ 21");             <strong>// All the money in the world</strong>
 * </pre></blockquote>
 *
 * <h2>Auto-Denomination versus Fixed-Denomination</h2>
 *
 * <p>There are two provided concrete classes, one that automatically denominates values to
 * be formatted, {@link BtcAutoFormat}, and another that formats any value in units of a
 * fixed, specified denomination, {@link BtcFixedFormat}.</p>
 *
 * <h3>Automatic Denomination</h3>
 *
 * <p>Automatic denomination means that the formatter adjusts the denominational units in which a
 * formatted number is expressed based on the monetary value that number represents.  An
 * auto-denominating formatter is defined by its style, specified by one of the enumerated
 * values of {@link BtcAutoFormat.Style}.  There are two styles constants: {@link
 * BtcAutoFormat.Style#CODE} (the default), and {@link BtcAutoFormat.Style#SYMBOL}.  The
 * difference is that the {@code CODE} style uses an internationally-distinct currency
 * code, such as {@code "BTC"}, to indicate the units of denomination, while the
 * {@code SYMBOL} style uses a possibly-ambiguous currency symbol such as
 * {@code "฿"}.</p>
 *
 * <p>The denomination used when formatting will be either bitcoin, millicoin
 * or microcoin, depending on the value being represented, chosen so as to minimize the number
 * of consecutive zeros displayed without losing precision.  For example, depending on the
 * locale, a value of one bitcoin might be formatted as {@code ฿1.00} where a value
 * exceeding that by one satoshi would be {@code µ฿1,000,000.01}</p>
 *
 * <h3>Fixed Denomination</h3>
 * 
 * <p>Fixed denomination means that the same denomination of units is used for every value that is
 * formatted or parsed by a given formatter instance.  A fixed-denomination formatter is
 * defined by its scale, which is the number of places one must shift the decimal point in
 * increasing precision to convert the representation of a given quantity of bitcoins into a
 * representation of the same value denominated in the formatter's units.  For example, a scale
 * value of {@code 3} specifies a denomination of millibitcoins, because to represent
 * {@code 1.0000 BTC}, or one bitcoin, in millibitcoins, one shifts the decimal point
 * three places, that is, to {@code 1000.0 mBTC}.</p>
 *
 * <h2>Construction</h2>
 *
 * <p>There are two ways to obtain an instance of this class:</p>
 *
 * <ol>
 *   <li>Use one of the factory methods; or
 *   <li>Use a {@link BtcFormat.Builder} object.</ol>
 *
 * <p>The factory methods are appropriate for basic use where the default
 * configuration is either used or modified.  The {@link Builder}
 * class provides more control over the configuration, and gives
 * access to some features not available through the factory methods,
 * such as using custom formatting patterns and currency symbols.</p>
 *
 * <h3>Factory Methods</h3>
 *
 * <p>Although formatting and parsing is performed by one of the concrete
 * subclasses, you can obtain formatters using the various static factory
 * methods of this abstract base class {@link BtcFormat}.  There
 * are a variety of overloaded methods that allow you to obtain a
 * formatter that behaves according to your needs.</p>
 *
 * <p>The primary distinction is between automatic- and
 * fixed-denomination formatters.  By default, the
 * {@code getInstance()} method with no arguments returns a new,
 * automatic-denominating {@link BtcAutoFormat} instance for your
 * default locale that will display exactly two fractional decimal
 * places and a currency code.  For example, if you happen to be in
 * the USA:</p>
 *
 * <blockquote><pre>
 * BtcFormat f = BtcFormat.getInstance();
 * String s = f.format(Coin.COIN); <strong>// "BTC 1.00"</strong>
 * </pre></blockquote>
 *
 * <p>The first argument to {@code getInstance()} can determine
 * whether you get an auto- or fixed-denominating formatter.  If the
 * type of the first argument is an {@code int}, then the value
 * of that {@code int} will be interpreted as the decimal-place scale of
 * the {@link BtcFixedFormat} instance that is returned, and thus will
 * determine its denomination.  For example, if you want to format
 * values in units of microbitcoins:</p>
 *
 * <blockquote><pre>BtcFormat m = BtcFormat.getInstance(6);
 * String s = m.format(Coin.COIN); <strong>// "1,000,000.00"</strong>
 * </pre></blockquote>
 *
 * <p>This class provides several constants bound to common scale values:</p>
 *
 * <blockquote><pre>BtcFormat milliFormat = BtcFormat.getInstance(MILLICOIN_SCALE);</pre></blockquote>
 *
 * <p>Alternatively, if the type of the first argument to
 * {@code getInstance()} is one of the enumerated values of the
 * {@link BtcAutoFormat.Style} type, either {@link BtcAutoFormat.Style#CODE} or
 * {@link BtcAutoFormat.Style#SYMBOL}, then you will get a {@link BtcAutoFormat}
 * instance that uses either a currency code or symbol, respectively,
 * to indicate the results of its auto-denomination.</p>
 *
 * <blockquote><pre>
 * BtcFormat s = BtcFormat.getInstance(SYMBOL);
 * Coin value = Coin.parseCoin("0.1234");
 * String mil = s.format(value);              <strong>// "₥฿123.40"</strong>
 * String mic = s.format(value.divide(1000)); <strong>// "µ฿123.40"</strong>
 * </pre></blockquote>
 *
 * <p>An alternative way to specify whether you want an auto- or fixed-denomination formatter
 * is to use one of the factory methods that is named to indicate that characteristics of the
 * new instance returned.  For fixed-denomination formatters, these methods are {@link
 * #getCoinInstance()}, {@link #getMilliInstance()}, and {@link #getMicroInstance()}.  These
 * three methods are equivalent to invoking {@code getInstance()} with a first argument of
 * {@code 0}, {@code 3} and {@code 6}, respectively.  For auto-denominating
 * formatters the relevant factory methods are {@link #getCodeInstance()} and {@link
 * #getSymbolInstance()}, which are equivalent to {@code getInstance(Style.CODE)}, and
 * {@code getInstance(Style.SYMBOL)}.</p>
 *
 * <p>Regardless of how you specify whether your new formatter is to be of automatic- or
 * fixed-denomination, the next (and possibly first) parameter to each of the factory methods
 * is an optional {@link Locale} value.</p>
 *
 * <p>For example, here we construct four instances for the same locale that each format
 * differently the same one-bitcoin value:</p>
 *
 * <blockquote><pre>
 * <strong>// Next line returns "1,00 BTC"</strong>
 * BtcFormat.getInstance(Locale.GERMANY).format(Coin.COIN);
 * <strong>// Next line returns "1,00 ฿"</strong>
 * BtcFormat.getInstance(SYMBOL, Locale.GERMANY).format(Coin.COIN);
 * <strong>// Next line returns "1.000,00"</strong>
 * BtcFormat.getMilliInstance(Locale.GERMANY).format(Coin.COIN);
 * <strong>// Next line returns "10.000,00"</strong>
 * BtcFormat.getInstance(4, Locale.GERMANY).format(Coin.COIN);
 * </pre></blockquote>
 *
 * <p>Omitting such a {@link Locale} parameter will give you a
 * formatter for your default locale.</p>
 *
 * <p>The final (and possibly only) arguments to the factory methods serve to set the default
 * number of fractional decimal places that will be displayed when formatting monetary values.
 * In the case of an auto-denominating formatter, this can be a single {@code int} value,
 * which will determine the number of fractional decimal places to be used in all cases, except
 * where either (1) doing so would provide a place for fractional satoshis, or (2) that default
 * value is overridden when invoking the {@code format()} method as described below.</p>
 *
 * <p>In the case of a fixed-denomination formatter, you can pass any number of
 * {@code int} values.  The first will determine the minimum number of fractional decimal
 * places, and each following {@code int} value specifies the size of an optional group of
 * decimal-places to be displayed only if useful for expressing precision.  As with auto-denominating
 * formatters, numbers will never be formatted with a decimal place that represents a
 * fractional quantity of satoshis, and these defaults can be overridden by arguments to the
 * {@code format()} method.  See below for examples.</p>
 *
 * <h3>The {@link Builder} Class</h3>
 *
 * <p>A new {@link BtcFormat.Builder} instance is returned by the {@link #builder()} method.  Such
 * an object has methods that set the configuration parameters of a {@link BtcFormat}
 * object.  Its {@link Builder#build()} method constructs and returns a {@link BtcFormat} instance
 * configured according to those settings.</p>
 *
 * <p>In addition to setter-methods that correspond to the factory-method parameters explained
 * above, a {@link Builder} also allows you to specify custom formatting and parsing
 * patterns and currency symbols and codes.  For example, rather than using the default
 * currency symbol, which has the same unicode character point as the national currency symbol of
 * Thailand, some people prefer to use a capital letter "B" with a vertical overstrike.</p>
 *
 * <blockquote><pre>
 * BtcFormat.Builder builder = BtcFormat.builder();
 * builder.style(SYMBOL);
 * builder.symbol("B&#x5c;u20e6"); <strong>// unicode char "double vertical stroke overlay"</strong>
 * BtcFormat f = builder.build();
 * String out = f.format(COIN); <strong>// "B⃦1.00" depending on locale</strong>
 * </pre></blockquote>
 *
 * The {@link Builder} methods are chainable.  So, for example, if you are
 * deferential to ISO 4217, you might construct a formatter in a single line this way:
 *
 * <blockquote><pre>
 * BtcFormat f = BtcFormat.builder().style(CODE).code("XBT").build();
 * String out = f.format(COIN); <strong>// "XBT 1.00"</strong>
 * </pre></blockquote>
 *
 * <p>See the documentation of the {@link BtcFormat.Builder} class for details.</p>
 *
 * <h2>Formatting</h2>
 *
 * <p>You format a Bitcoin monetary value by passing it to the {@link BtcFormat#format(Object)}
 * method.  This argument can be either a {@link Coin}-type object or a
 * numerical object such as {@link Long} or {@link BigDecimal}.
 * Integer-based types such as {@link BigInteger} are interpreted as representing a
 * number of satoshis, while a {@link BigDecimal} is interpreted as representing a
 * number of bitcoins.  A value having a fractional amount of satoshis is rounded to the
 * nearest whole satoshi at least, and possibly to a greater unit depending on the number of
 * fractional decimal-places displayed.  The {@code format()} method will not accept an
 * argument whose type is {@link String}, {@link Float} nor {@link Double}.</p>
 *
 * <p>Subsequent to the monetary value to be formatted, the {@link #format(Object)} method also
 * accepts as arguments optional {@code int} values that specify the number of decimal
 * places to use to represent the fractional portion of the number.  This overrides the
 * default, and enables a single formatter instance to be reused, formatting different values
 * that require different numbers of fractional decimal places.  These parameters have the same
 * meaning as those that set the default values in the factory methods as described above.
 * Namely, a single {@code int} value determines the minimum number of fractional decimal
 * places that will be used in all cases, to a precision limit of satoshis.  Instances of
 * {@link BtcFixedFormat} also accept a variable-length sequence of additional {@code int}
 * values, each of which specifies the size of a group of fractional decimal-places to be used
 * in addition to all preceding places, only if useful to express precision, and only to a
 * maximum precision of satoshis.  For example:</p>
 *
 * <blockquote><pre>
 * BtcFormat f = BtcFormat.getCoinInstance();
 * Coin value = COIN.add(Coin.valueOf(5)); <strong>// 100000005 satoshis</strong>
 * f.format(value, 2);       <strong>// "1.00"</strong>
 * f.format(value, 3);       <strong>// "1.000"</strong>
 * f.format(value, 2, 3);    <strong>// "1.00" three more zeros doesn't help </strong>
 * f.format(value, 2, 3, 3); <strong>// "1.00000005" </strong>
 * f.format(value, 2, 3, 4); <strong>// "1.00000005" fractions of satoshis have no place</strong>
 * f.format(value, 2, 3, 2); <strong>// "1.0000001" rounds to nearest usable place</strong>
 * </pre></blockquote>
 *
 * <p>Note that if using all the fractional decimal places in a specified group would give a
 * place to fractions of satoshis, then the size of that group will be reduced to a maximum
 * precision of satoshis.  Either all or none of the allowed decimal places of that group will
 * still be applied as doing so is useful for expressing the precision of the value being
 * formatted.</p>
 *
 * <p>Several convenient constants of repeating group-size sequences are provided:
 * {@link BtcFixedFormat#REPEATING_PLACES}, {@link
 * BtcFixedFormat#REPEATING_DOUBLETS} and {@link
 * BtcFixedFormat#REPEATING_TRIPLETS}.  These signify repeating groups
 * of one, two and three decimals places, respectively.  For example,
 * to display only as many fractional places as useful in order to
 * prevent hanging zeros on the least-significant end of formatted
 * numbers:</p>
 *
 * <blockquote><pre>
 * format(value, 0, REPEATING_PLACES);
 * </pre></blockquote>
 *
 * <p>When using an automatically-denominating formatter, you might
 * want to know what denomination was chosen.  You can get the
 * currency-units indicator, as well as any other field in the
 * formatted output, by using a {@link FieldPosition} instance
 * constructed using an appropriate constant from the {@link
 * NumberFormat.Field} class:</p>
 *
 * <blockquote><pre>
 * BtcFormat de = BtcFormat.getInstance(Locale.GERMANY);
 * FieldPosition currField = new FieldPosition(NumberFormat.Field.CURRENCY);
 * <strong>// next line formats the value as "987.654.321,23 µBTC"</strong>
 * String output = de.format(valueOf(98765432123L), new StringBuffer(), currField);
 * <strong>// next line sets variable currencyCode to "µBTC"</strong>
 * String currencyCode = output.substring(currField.getBeginIndex(), currField.getEndIndex()));
 * </pre></blockquote>
 *
 * <p>When using a fixed-denomination formatter whose scale can be expressed as a standard
 * "metric" prefix, you can invoke the {@code code()} and {@code symbol()} methods to
 * obtain a {@link String} whose value is the appropriate currency code or symbol,
 * respectively, for that formatter.</p>
 *
 * <blockquote><pre>
 * BtcFixedFormat kilo = (BtcFixedFormat)BtcFormat(-3); <strong>// scale -3 for kilocoins</strong>
 * Coin value = Coin.parseCoin("1230");
 * <strong>// variable coded will be set to "kBTC 1.23"</strong>
 * String coded = kilo.code() + " " + kilo.format(value);
 * <strong>// variable symbolic will be set to "k฿1.23"</strong>
 * String symbolic = kilo.symbol() + kilo.format(value);
 * BtcFormat(4).code(); <strong>// unnamed denomination has no code; raises exception</strong>
 * </pre></blockquote>
 *
 * <h3>Formatting for Tabular Columns</h3>
 *
 * <p>When displaying tables of monetary values, you can lessen the
 * risk of human misreading-error by vertically aligning the decimal
 * separator of those values.  This example demonstrates one way to do that:</p>
 *
 * <blockquote><pre>
 * <strong>// The elements of this array are the values we will format:</strong>
 * Coin[] rows = {MAX_MONEY, MAX_MONEY.subtract(SATOSHI), Coin.parseCoin("1234"),
 *                COIN, COIN.divide(1000),
 *                valueOf(10000), valueOf(1000), valueOf(100),
 *                SATOSHI};
 * BtcFormat f = BtcFormat.getCoinInstance(2, REPEATING_PLACES);
 * FieldPosition fp = new FieldPosition(DECIMAL_SEPARATOR); <strong>// see java.text.NumberFormat.Field</strong>
 * String[] output = new String[rows.length];
 * int[] indexes = new int[rows.length];
 * int maxIndex = 0;
 * for (int i = 0; i &lt; rows.length; i++) {
 *     output[i] = f.format(rows[i], new StringBuffer(), fp).toString();
 *     indexes[i] = fp.getBeginIndex();
 *     if (indexes[i] &gt; maxIndex) maxIndex = indexes[i];
 * }
 * for (int i = 0; i &lt; output.length; i++) {
 *     System.out.println(repeat(" ", (maxIndex - indexes[i])) + output[i]);
 * }
 * </pre></blockquote>
 *
 * Assuming you are using a monospaced font, and depending on your
 * locale, the foregoing will print the following:
 *
 * <blockquote><pre>
 * 21,000,000.00
 * 20,999,999.99999999
 *      1,234.00
 *          1.00
 *          0.001
 *          0.0001
 *          0.00001
 *          0.000001
 *          0.00000001
 * </pre></blockquote>
 *
 * <p>If you need to vertically-align columns printed in a proportional font,
 * then see the documentation for the {@link NumberFormat} class
 * for an explanation of how to do that.</p>
 *
 * <h2>Parsing</h2>
 *
 * <p>The {@link #parse(String)} method accepts a {@link String} argument, and returns a
 * {@link Coin}-type value.  The difference in parsing behavior between instances of {@link
 * BtcFixedFormat} and {@link BtcAutoFormat} is analogous to the difference in formatting
 * behavior between instances of those classes.  Instances of {@link BtcAutoFormat} recognize
 * currency codes and symbols in the {@link String} being parsed, and interpret them as
 * indicators of the units in which the number being parsed is denominated.  On the other hand,
 * instances of {@link BtcFixedFormat} by default recognize no codes nor symbols, but rather
 * interpret every number as being denominated in the units that were specified when
 * constructing the instance doing the parsing.  This default behavior of {@link
 * BtcFixedFormat} can be overridden by setting a parsing pattern that includes a currency sign
 * using the {@link BtcFormat.Builder#pattern()} method.</p>
 *
 * <p>The {@link BtcAutoFormat#parse(String)} method of {@link BtcAutoFormat} (and of
 * {@link BtcAutoFormat} configured with applicable non-default pattern) will recognize a
 * variety of currency symbols and codes, including all standard international (metric)
 * prefixes from micro to mega.  For example, denominational units of microcoins may be
 * specified by {@code µ฿}, {@code u฿}, {@code µB⃦}, {@code µɃ},
 * {@code µBTC} or other appropriate permutations of those characters.  Additionally, if
 * either or both of a custom currency code or symbol is configured using {@link
 * BtcFormat.Builder#code} or {@link BtcFormat.Builder#code}, then such code or symbol will
 * be recognized in addition to those recognized by default.</p>
 *
 * <p>Instances of this class that recognize currency signs will recognize both currency
 * symbols and codes, regardless of which that instance uses for formatting.  However, if the
 * style is {@code CODE} (and unless overridden by a custom pattern) then a space character must
 * separate the units indicator from the number.  When parsing with a {@code SYMBOL}-style
 * {@code BtcFormat} instance, on the other hand, whether or not the units indicator must
 * be separated by a space from the number is determined by the locale.  The {@link
 * BtcFormat#pattern()} method returns a representation of the pattern that
 * can be examined to determine whether a space must separate currency signs from numbers in
 * parsed {@link String}s.</p>
 *
 * <p>When parsing, if the currency-units indicator is absent, then a {@link BtcAutoFormat}
 * instance will infer a denomination of bitcoins while a {@link BtcFixedFormat} will infer the
 * denomination in which it expresses formatted values.  Note: by default (unless overridden by
 * a custom pattern), if the locale or style requires a space to separate the number from the
 * units indicator, that space must be present in the String to be parsed, even if the units
 * indicator is absent.</p>
 *
 * <p>The {@code parse()} method returns an instance of the
 * {@link Coin} class.  Therefore, attempting to parse a value greater
 * than the maximum that a {@code Coin} object can represent will
 * raise a {@code ParseException}, as will any other detected
 * parsing error.</p>
 *
 *  <h2>Limitations</h2>
 *
 *  <h3>Parsing</h3>
 *
 * <p>Parsing is performed by an underlying {@link NumberFormat} object. While this
 * delivers the benefit of recognizing locale-specific patterns, some have criticized other
 * aspects of its behavior.  For example, see <a
 * href="http://www.ibm.com/developerworks/library/j-numberformat/">this article by Joe Sam
 * Shirah</a>.  In particular, explicit positive-signs are not recognized.  If you are parsing
 * input from end-users, then you should consider whether you would benefit from any of the
 * work-arounds mentioned in that article.</p>
 *
 * <h3>Exotic Locales</h3>
 *
 * <p>This class is not well-tested in locales that use non-ascii
 * character sets, especially those where writing proceeds from
 * right-to-left.  Helpful feedback in that regard is appreciated.</p>
 *
 * <h2>Thread-Safety</h2>
 *
 * <p>Instances of this class are immutable.</p>
 *
 * @see Format
 * @see NumberFormat
 * @see DecimalFormat
 * @see DecimalFormatSymbols
 * @see FieldPosition
 * @see Coin
 */

public abstract class BtcFormat extends Format {

    /* CONCURRENCY NOTES
     *
     * There is one mutable member of this class, the `DecimalFormat` object bound to variable
     * `numberFormat`.  The relevant methods invoked on it are: setMinimumFractionDigits(),
     * setMaximumFractionDigits(), and setDecimalFormatSymbols(), along with the respective
     * getter methods corresponding to each.  The first two methods are used to set the number
     * of fractional decimal places displayed when formatting, which is reflected in the
     * patterns returned by the public pattern() and localizedPattern() methods.  The last
     * method sets the value of that object's member `DecimalFormatSymbols` object for
     * formatting and parsing, which is also reflected in the aforementioned patterns.  The
     * patterns, which are the passed-through return values of the DecimalFormat object's
     * toPattern() and toLocalizedPattern() methods, and the value of the DecimalFormat
     * object's DecimalFormatSymbols member are among the values compared between instances of
     * this class in determining the return values of the `equals()` and `hashCode()` methods.
     *
     * From the foregoing, you can understand that immutability is achieved as follows: access
     * to the variable `numberFormat` referent's fraction-digits and format-symbols fields are
     * synchronized on that DecimalFormat object.  The state of those fraction-digits limits
     * and decimal-format symbols must be returned to a static state after being changed for
     * formatting or parsing since the user can see them reflected in the return values of
     * above-mentioned methods and because `equals()` and `hashCode()` use them for
     * comparisons.
     */

    /** The conventional international currency code for bitcoins: "BTC" */
    private static final String COIN_CODE = "BTC";
    /** The default currency symbols for bitcoins */
    private static final String COIN_SYMBOL = "฿";
    /** An alternative currency symbol to use in locales where the default symbol is used for the national currency. */
    protected static final String COIN_SYMBOL_ALT = "Ƀ";

    protected final DecimalFormat numberFormat; // warning: mutable
    protected final int minimumFractionDigits;
    protected final List<Integer> decimalGroups;

    /* Scale is the number of decimal-places difference from same value in bitcoins */
    /** A constant useful for specifying a denomination of bitcoins, the {@code int} value
     *  {@code 0}. */
    public static final int COIN_SCALE = 0;

    /** A constant useful for specifying a denomination of millibitcoins, the {@code int}
     *  value {@code 3}. */
    public static final int MILLICOIN_SCALE = 3;

    /** A constant useful for specifying a denomination of microbitcoins, the {@code int}
     *  value {@code 6}. */
    public static final int MICROCOIN_SCALE = 6;

    /** Return the number of decimal places by which any value denominated in the
     *  units indicated by the given scale differs from that same value denominated in satoshis */
    private static int offSatoshis(int scale) { return Coin.SMALLEST_UNIT_EXPONENT - scale; }

    private static Locale defaultLocale() { return Locale.getDefault(); }

    /**
     * <p>This class constructs new instances of {@link BtcFormat}, allowing for the
     * configuration of those instances before they are constructed.  After obtaining a
     * {@code Builder} object from the {@link BtcFormat#builder()} method, invoke the
     * necessary setter methods to obtain your desired configuration.  Finally, the {@link
     * #build()} method returns a new {@code BtcFormat} object that has the specified
     * configuration.
     *
     * <p>All the setter methods override defaults.  Invoking {@code build()} without invoking any
     * of the setting methods is equivalent to invoking {@link BtcFormat#getInstance()} with no arguments.
     *
     * <p>Each setter methods returns the same instance on which it is invoked,
     *  thus these methods can be chained.
     *
     * <p>Instances of this class are <strong>not</strong> thread-safe.
     */
    public static class Builder {

        private enum Variant {
            AUTO { @Override BtcFormat newInstance(Builder b) {
                       return getInstance(b.style, b.locale, b.minimumFractionDigits);
                   }},
            FIXED,
            UNSET;
            BtcFormat newInstance(Builder b) {
                return getInstance(b.scale, b.locale, b.minimumFractionDigits, b.fractionGroups);
            }
        }
        // Parameters are initialized to default or unset values
        private Variant variant = Variant.UNSET;
        private Locale locale = defaultLocale();
        private int minimumFractionDigits = 2;
        private int[] fractionGroups = {};
        private Style style = BtcAutoFormat.Style.CODE;
        private int scale = 0;
        private String symbol = "",code = "",pattern = "",localizedPattern = "";

        private Builder() {}

        /** Specify the new {@code BtcFormat} is to be automatically-denominating.
         * The argument determines which of either codes or symbols the new {@code BtcFormat}
         * will use by default to indicate the denominations it chooses when formatting values.
         *
         * <p>Note that the {@code Style} argument specifies the
         * <em>default</em> style, which is overridden by invoking
         * either {@link #pattern(String)} or {@link #localizedPattern(String)}.
         *
         * @throws IllegalArgumentException if {@link #scale(int)} has
         *         previously been invoked on this instance.*/
        public Builder style(BtcAutoFormat.Style val) {
            if (variant == Variant.FIXED)
                throw new IllegalStateException("You cannot invoke both style() and scale()");
            variant = Variant.AUTO;
            style = val;
            return this;
        }

        /** Specify the number of decimal places in the fraction part of formatted numbers.
         * This is equivalent to the {@link #minimumFractionDigits(int)} method, but named
         * appropriately for the context of generating {@link BtcAutoFormat} instances.
         *
         *  <p>If neither this method nor {@code minimumFactionDigits()} is invoked, the default value
         *  will be {@code 2}. */
        public Builder fractionDigits(int val) { return minimumFractionDigits(val); }

        /** Specify a fixed-denomination of units to use when formatting and parsing values.
         *  The argument specifies the number of decimal places, in increasing
         *  precision, by which each formatted value will differ from that same value
         *  denominated in bitcoins.  For example, a denomination of millibitcoins is specified
         *  with a value of {@code 3}.
         *
         * <p>The {@code BtcFormat} class provides appropriately named
         * {@code int}-type constants for the three common values, {@link BtcFormat#COIN_SCALE},
         * {@link BtcFormat#MILLICOIN_SCALE} {@link BtcFormat#MICROCOIN_SCALE}.
         *
         * <p>If neither this method nor {@link #style(BtcAutoFormat.Style)} is invoked on a
         * {@link Builder}, then the {@link BtcFormat} will default to a
         * fixed-denomination of bitcoins, equivalent to invoking this method with an argument
         * of {@code 0}. */
        public Builder scale(int val) {
            if (variant == Variant.AUTO)
                throw new IllegalStateException("You cannot invoke both scale() and style()");
            variant = Variant.FIXED;
            scale = val;
            return this;
        }

        /** Specify the minimum number of decimal places in the fraction part of formatted values.
         *  This method is equivalent to {@link #fractionDigits(int)}, but named appropriately for
         *  the context of generating a fixed-denomination formatter.
         *
         *  <p>If neither this method nor {@code fractionDigits()} is invoked, the default value
         *  will be {@code 2}.  */
        public Builder minimumFractionDigits(int val) { minimumFractionDigits = val;  return this; }

        /** Specify the sizes of a variable number of optional decimal-place groups in the
         *  fraction part of formatted values.  A group of each specified size will be used in
         *  addition to all previously applied decimal places only if doing so is useful for
         *  expressing precision.  The size of each group is limited to a maximum precision of
         *  satoshis.
         *
         *  <p>If this method is not invoked, then the number of fractional decimal places will
         *  be limited to the value passed to {@link #minimumFractionDigits}, or {@code 2}
         *  if that method is not invoked. */
        public Builder fractionGroups(int... val) { fractionGroups = val; return this; }

        /** Specify the {@link Locale} for formatting and parsing.
         *  If this method is not invoked, then the runtime default locale will be used. */
        public Builder locale(Locale val) { locale = val; return this; }

        /** Specify a currency symbol to be used in the denomination-unit indicators
         *  of formatted values.  This method only sets the symbol, but does not cause
         *  it to be used.  You must also invoke either {@code style(SYMBOL)}, or else apply
         *  a custom pattern that includes a single currency-sign character by invoking either
         *  {@link #pattern(String)} or {@link #localizedPattern(String)}.
         *
         *  <p>Specify only the base symbol.  The appropriate prefix will be applied according
         *  to the denomination of formatted and parsed values. */
        public Builder symbol(String val) { symbol = val; return this; }

        /** Specify a custom currency code to be used in the denomination-unit indicators
         *  of formatted values.  This method only sets the code, but does not cause
         *  it to be used.  You must also invoke either {@code style(CODE)}, or else apply
         *  a custom pattern that includes a double currency-sign character by invoking either
         *  {@link #pattern(String)} or {@link #localizedPattern(String)}.
         *
         *  <p>Specify only the base code.  The appropriate prefix will be applied according
         *  to the denomination of formatted and parsed values. */
        public Builder code(String val) { code = val; return this; }

        /** Use the given pattern when formatting and parsing.  The format of this pattern is
         *  identical to that used by the {@link DecimalFormat} class.
         *
         *  <p>If the pattern lacks a negative subpattern, then the formatter will indicate
         *  negative values by placing a minus sign immediately preceding the number part of
         *  formatted values.
         *
         *  <p>Note that while the pattern format specified by the {@link
         *  java.text.DecimalFormat} class includes a mechanism for setting the number of
         *  fractional decimal places, that part of the pattern is ignored.  Instead, use the
         *  {@link #fractionDigits(int)}, {@link #minimumFractionDigits(int)} and {@link
         *  #fractionGroups(int...)} methods.
         *
         *  <p>Warning: if you set a pattern that includes a currency-sign for a
         *  fixed-denomination formatter that uses a non-standard scale, then an exception will
         *  be raised when you try to format a value.  The standard scales include all for
         *  which a metric prefix exists from micro to mega.
         *
         *  <p>Note that by applying a pattern you override the configured formatting style of
         *  {@link BtcAutoFormat} instances.  */
        public Builder pattern(String val) {
            if (!Strings.isNullOrEmpty(localizedPattern))
                throw new IllegalStateException("You cannot invoke both pattern() and localizedPattern()");
            pattern = val;
            return this;
        } 

        /** Use the given localized-pattern for formatting and parsing.  The format of this
         *  pattern is identical to the patterns used by the {@link DecimalFormat}
         *  class.
         *
         *  <p>The pattern is localized according to the locale of the {@code BtcFormat}
         *  instance, the symbols for which can be examined by inspecting the {@link
         *  java.text.DecimalFormatSymbols} object returned by {@link BtcFormat#symbols()}.
         *  So, for example, if you are in Germany, then the non-localized pattern of
         *  <pre>"#,##0.###"</pre> would be localized as <pre>"#.##0,###"</pre>
         *
         *  <p>If the pattern lacks a negative subpattern, then the formatter will indicate
         *  negative values by placing a minus sign immediately preceding the number part of
         *  formatted values.
         *
         *  <p>Note that while the pattern format specified by the {@link
         *  java.text.DecimalFormat} class includes a mechanism for setting the number of
         *  fractional decimal places, that part of the pattern is ignored.  Instead, use the
         *  {@link #fractionDigits(int)}, {@link #minimumFractionDigits(int)} and {@link
         *  #fractionGroups(int...)} methods.
         *
         *  <p>Warning: if you set a pattern that includes a currency-sign for a
         *  fixed-denomination formatter that uses a non-standard scale, then an exception will
         *  be raised when you try to format a value.  The standard scales include all for
         *  which a metric prefix exists from micro to mega.
         *
         *  <p>Note that by applying a pattern you override the configured formatting style of
         *  {@link BtcAutoFormat} instances.         */
        public Builder localizedPattern(String val) {
            if (!Strings.isNullOrEmpty(pattern))
                throw new IllegalStateException("You cannot invoke both pattern() and localizedPattern().");
            localizedPattern = val;
            return this;
        }

        /** Return a new {@link BtcFormat} instance.  The object returned will be configured according
         *  to the state of this {@code Builder} instance at the time this method is invoked. */
        public BtcFormat build() {
            BtcFormat f = variant.newInstance(this);
            if (!Strings.isNullOrEmpty(symbol) || !Strings.isNullOrEmpty(code)) { synchronized(f.numberFormat) {
                DecimalFormatSymbols defaultSigns = f.numberFormat.getDecimalFormatSymbols();
                setSymbolAndCode(f.numberFormat,
                        !Strings.isNullOrEmpty(symbol) ? symbol : defaultSigns.getCurrencySymbol(),
                        !Strings.isNullOrEmpty(code) ? code : defaultSigns.getInternationalCurrencySymbol()
                );
            }}
            if (!Strings.isNullOrEmpty(localizedPattern) || !Strings.isNullOrEmpty(pattern)) {
                int places = f.numberFormat.getMinimumFractionDigits();
                if (!Strings.isNullOrEmpty(localizedPattern)) f.numberFormat.applyLocalizedPattern(negify(localizedPattern));
                else f.numberFormat.applyPattern(negify(pattern));
                f.numberFormat.setMinimumFractionDigits(places);
                f.numberFormat.setMaximumFractionDigits(places);
            }
            return f;
        }

    }

    /** Return a new {@link Builder} object.  See the documentation of that class for usage details. */
    public static Builder builder() { return new Builder(); }

    /** This single constructor is invoked by the overriding subclass constructors. */
    protected BtcFormat(DecimalFormat numberFormat, int minDecimals, List<Integer> groups) {
        checkArgument(minDecimals >= 0, () ->
                "there can be no fewer than zero fractional decimal places");
        this.numberFormat = numberFormat;
        this.numberFormat.setParseBigDecimal(true);
        this.numberFormat.setRoundingMode(HALF_UP);
        this.minimumFractionDigits = minDecimals;
        this.numberFormat.setMinimumFractionDigits(this.minimumFractionDigits);
        this.numberFormat.setMaximumFractionDigits(this.minimumFractionDigits);
        this.decimalGroups = groups;
        synchronized (this.numberFormat) { setSymbolAndCode(
            this.numberFormat,
            (this.numberFormat.getDecimalFormatSymbols().getCurrencySymbol().contains(COIN_SYMBOL))
                ? COIN_SYMBOL_ALT
                : COIN_SYMBOL,
            COIN_CODE
        );}
    }

    /**
     * Return a new instance of this class using all defaults.  The returned formatter will
     * auto-denominate values so as to minimize zeros without loss of precision and display a
     * currency code, for example "{@code BTC}", to indicate that denomination.  The
     * returned object will uses the default locale for formatting the number and placement of
     * the currency-code.  Two fractional decimal places will be displayed in all formatted numbers.
     */
    public static BtcFormat getInstance() { return getInstance(defaultLocale()); }

    /**
     * Return a new auto-denominating instance that will indicate units using a currency
     * symbol, for example, {@code "฿"}.  Formatting and parsing will be done
     * according to the default locale.
     */
    public static BtcFormat getSymbolInstance() { return getSymbolInstance(defaultLocale()); }

    /**
     * Return a new auto-denominating instance that will indicate units using a currency
     * code, for example, {@code "BTC"}.  Formatting and parsing will be done
     * according to the default locale.
     */
    public static BtcFormat getCodeInstance() { return getCodeInstance(defaultLocale()); }

    /**
     * Return a new symbol-style auto-formatter with the given number of fractional decimal
     * places.  Denominational units will be indicated using a currency symbol, for example,
     * {@code "฿"}.  The returned object will format the fraction-part of numbers using
     * the given number of decimal places, or fewer as necessary to avoid giving a place to
     * fractional satoshis.  Formatting and parsing will be done according to the default
     * locale.
     */
    public static BtcFormat getSymbolInstance(int fractionPlaces) {
        return getSymbolInstance(defaultLocale(), fractionPlaces);
    }

    /**
     * Return a new code-style auto-formatter with the given number of fractional decimal
     * places.  Denominational units will be indicated using a currency code, for example,
     * {@code "BTC"}.  The returned object will format the fraction-part of numbers using
     * the given number of decimal places, or fewer as necessary to avoid giving a place to
     * fractional satoshis.  Formatting and parsing will be done according to the default
     * locale.
     */
    public static BtcFormat getCodeInstance(int minDecimals) {
        return getCodeInstance(defaultLocale(), minDecimals);
    }

    /**
     * Return a new code-style auto-formatter for the given locale.  The returned object will
     * select denominational units based on each value being formatted, and will indicate those
     * units using a currency code, for example, {@code "mBTC"}.
     */
    public static BtcFormat getInstance(Locale locale) { return getCodeInstance(locale); }

    /**
     * Return a new code-style auto-formatter for the given locale.  The returned object will
     * select denominational units based on each value being formatted, and will indicate those
     * units using a currency code, for example, {@code "mBTC"}.
     */
    public static BtcFormat getCodeInstance(Locale locale) { return getInstance(CODE, locale); }

    /**
     * Return a new code-style auto-formatter for the given locale with the given number of
     * fraction places.  The returned object will select denominational units based on each
     * value being formatted, and will indicate those units using a currency code, for example,
     * {@code "mBTC"}.  The returned object will format the fraction-part of numbers using
     * the given number of decimal places, or fewer as necessary to avoid giving a place to
     * fractional satoshis.
     */
    public static BtcFormat getInstance(Locale locale, int minDecimals) {
        return getCodeInstance(locale, minDecimals);
    }

    /**
     * Return a new code-style auto-formatter for the given locale with the given number of
     * fraction places.  The returned object will select denominational units based on each
     * value being formatted, and will indicate those units using a currency code, for example,
     * {@code "mBTC"}.  The returned object will format the fraction-part of numbers using
     * the given number of decimal places, or fewer as necessary to avoid giving a place to
     * fractional satoshis.
     */
    public static BtcFormat getCodeInstance(Locale locale, int minDecimals) {
        return getInstance(CODE, locale, minDecimals);
    }

    /**
     * Return a new symbol-style auto-formatter for the given locale.  The returned object will
     * select denominational units based on each value being formatted, and will indicate those
     * units using a currency symbol, for example, {@code "µ฿"}.
     */
    public static BtcFormat getSymbolInstance(Locale locale) {
        return getInstance(SYMBOL, locale);
    }

    /**
     * Return a new symbol-style auto-formatter for the given locale with the given number of
     * fraction places.  The returned object will select denominational units based on each
     * value being formatted, and will indicate those units using a currency symbol, for example,
     * {@code "µ฿"}.  The returned object will format the fraction-part of numbers using
     * the given number of decimal places, or fewer as necessary to avoid giving a place to
     * fractional satoshis.
     */
    public static BtcFormat getSymbolInstance(Locale locale, int fractionPlaces) {
        return getInstance(SYMBOL, locale, fractionPlaces);
    }

    /**
     * Return a new auto-denominating formatter.  The returned object will indicate the
     * denominational units of formatted values using either a currency symbol, such as,
     * {@code "฿"}, or code, such as {@code "mBTC"}, depending on the value of
     * the argument.  Formatting and parsing will be done according to the default locale.
     */
    public static BtcFormat getInstance(Style style) { return getInstance(style, defaultLocale()); }

    /**
     * Return a new auto-denominating formatter with the given number of fractional decimal
     * places.  The returned object will indicate the denominational units of formatted values
     * using either a currency symbol, such as, {@code "฿"}, or code, such as
     * {@code "mBTC"}, depending on the value of the first argument.  The returned object
     * will format the fraction-part of numbers using the given number of decimal places, or
     * fewer as necessary to avoid giving a place to fractional satoshis.  Formatting and
     * parsing will be done according to the default locale.
     */
    public static BtcFormat getInstance(Style style, int fractionPlaces) {
        return getInstance(style, defaultLocale(), fractionPlaces);
    }

    /**
     * Return a new auto-formatter with the given style for the given locale.
     * The returned object that will auto-denominate each formatted value, and
     * will indicate that denomination using either a currency code, such as
     * {@code "BTC"}, or symbol, such as {@code "฿"}, depending on the value
     * of the first argument. 
     * <p>The number of fractional decimal places in formatted number will be two, or fewer
     * as necessary to avoid giving a place to fractional satoshis.
     */
    public static BtcFormat getInstance(Style style, Locale locale) {
        return getInstance(style, locale, 2);
    }

    /**
     * Return a new auto-formatter for the given locale with the given number of fraction places.
     * The returned object will automatically-denominate each formatted
     * value, and will indicate that denomination using either a currency code,
     * such as {@code "mBTC"}, or symbol, such as {@code "฿"},
     * according to the given style argument.  It will format each number
     * according to the given locale.
     *
     * <p>The third parameter is the number of fractional decimal places to use for each
     * formatted number, reduced as necessary when formatting to avoid giving a place to
     * fractional satoshis.
     */
    public static BtcFormat getInstance(Style style, Locale locale, int fractionPlaces) {
        return new BtcAutoFormat(locale, style, fractionPlaces);
    }

    /**
     * Return a new coin-denominated formatter.  The returned object will format and parse
     * values according to the default locale, and will format numbers with two fractional
     * decimal places, rounding values as necessary.
     */
    public static BtcFormat getCoinInstance() { return getCoinInstance(defaultLocale()); }

    private static List<Integer> boxAsList(int[] elements) throws IllegalArgumentException {
        List<Integer> list = new ArrayList<>(elements.length);
        for (int e : elements) {
            checkArgument(e > 0, () ->
                    "size of decimal group must be at least one.");
            list.add(e);
        }
        return list;
    }

    /**
     * Return a new coin-denominated formatter with the specified fraction-places.  The
     * returned object will format and parse values according to the default locale, and will
     * format the fraction part of numbers with at least two decimal places.  The sizes of
     * additional groups of decimal places can be specified by a variable number of
     * {@code int} arguments.  Each optional decimal-place group will be applied only if
     * useful for expressing precision, and will be only partially applied if necessary to
     * avoid giving a place to fractional satoshis.
     */
    public static BtcFormat getCoinInstance(int minFractionPlaces, int... groups) {
        return getInstance(COIN_SCALE, defaultLocale(), minFractionPlaces, boxAsList(groups));
    }

    /**
     * Return a new coin-denominated formatter for the given locale.  The returned object will
     * format the fractional part of numbers with two decimal places, rounding as necessary.
     */
    public static BtcFormat getCoinInstance(Locale locale) {
        return getInstance(COIN_SCALE, locale, 2);
    }

    /**
     * Return a newly-constructed instance for the given locale that will format
     * values in terms of bitcoins, with the given minimum number of fractional
     * decimal places.  Optionally, repeating integer arguments can be passed, each
     * indicating the size of an additional group of fractional decimal places to be
     * used as necessary to avoid rounding, to a limiting precision of satoshis.
     */
    public static BtcFormat getCoinInstance(Locale locale, int scale, int... groups) {
        return getInstance(COIN_SCALE, locale, scale, boxAsList(groups));
    }

    /**
     * Return a new millicoin-denominated formatter.  The returned object will format and
     * parse values for the default locale, and will format the fractional part of numbers with
     * two decimal places, rounding as necessary.
     */
    public static BtcFormat getMilliInstance() { return getMilliInstance(defaultLocale()); }

    /**
     * Return a new millicoin-denominated formatter for the given locale.  The returned object
     * will format the fractional part of numbers with two decimal places, rounding as
     * necessary.
     */
    public static BtcFormat getMilliInstance(Locale locale) {
        return getInstance(MILLICOIN_SCALE, locale, 2);
    }

    /**
     * Return a new millicoin-denominated formatter with the specified fractional decimal
     * placing.  The returned object will format and parse values according to the default
     * locale, and will format the fractional part of numbers with the given minimum number of
     * fractional decimal places.  Optionally, repeating integer arguments can be passed, each
     * indicating the size of an additional group of fractional decimal places to be used as
     * necessary to avoid rounding, to a limiting precision of satoshis.
     */
    public static BtcFormat getMilliInstance(int scale, int... groups) {
        return getInstance(MILLICOIN_SCALE, defaultLocale(), scale, boxAsList(groups));
    }

    /**
     * Return a new millicoin-denominated formatter for the given locale with the specified
     * fractional decimal placing.  The returned object will format the fractional part of
     * numbers with the given minimum number of fractional decimal places.  Optionally,
     * repeating integer arguments can be passed, each indicating the size of an additional
     * group of fractional decimal places to be used as necessary to avoid rounding, to a
     * limiting precision of satoshis.
     */
    public static BtcFormat getMilliInstance(Locale locale, int scale, int... groups) {
        return getInstance(MILLICOIN_SCALE, locale, scale, boxAsList(groups));
    }

    /**
     * Return a new microcoin-denominated formatter for the default locale.  The returned object
     * will format the fractional part of numbers with two decimal places, rounding as
     * necessary.
     */
    public static BtcFormat getMicroInstance() { return getMicroInstance(defaultLocale()); }

    /**
     * Return a new microcoin-denominated formatter for the given locale.  The returned object
     * will format the fractional part of numbers with two decimal places, rounding as
     * necessary.
     */
    public static BtcFormat getMicroInstance(Locale locale) {
        return getInstance(MICROCOIN_SCALE, locale);
    }

    /**
     * Return a new microcoin-denominated formatter with the specified fractional decimal
     * placing.  The returned object will format and parse values according to the default
     * locale, and will format the fractional part of numbers with the given minimum number of
     * fractional decimal places.  Optionally, repeating integer arguments can be passed, each
     * indicating the size of an additional group of fractional decimal places to be used as
     * necessary to avoid rounding, to a limiting precision of satoshis.
     */
    public static BtcFormat getMicroInstance(int scale, int... groups) {
        return getInstance(MICROCOIN_SCALE, defaultLocale(), scale, boxAsList(groups));
    }

    /**
     * Return a new microcoin-denominated formatter for the given locale with the specified
     * fractional decimal placing.  The returned object will format the fractional part of
     * numbers with the given minimum number of fractional decimal places.  Optionally,
     * repeating integer arguments can be passed, each indicating the size of an additional
     * group of fractional decimal places to be used as necessary to avoid rounding, to a
     * limiting precision of satoshis.
     */
    public static BtcFormat getMicroInstance(Locale locale, int scale, int... groups) {
        return getInstance(MICROCOIN_SCALE, locale, scale, boxAsList(groups));
    }

    /**
     * Return a new fixed-denomination formatter with the specified fractional decimal
     * placing.  The first argument specifies the denomination as the size of the
     * shift from coin-denomination in increasingly-precise decimal places.  The returned object will format
     * and parse values according to the default locale, and will format the fractional part of
     * numbers with the given minimum number of fractional decimal places.  Optionally,
     * repeating integer arguments can be passed, each indicating the size of an additional
     * group of fractional decimal places to be used as necessary to avoid rounding, to a
     * limiting precision of satoshis.
     */
    public static BtcFormat getInstance(int scale, int minDecimals, int... groups) {
        return getInstance(scale, defaultLocale(), minDecimals, boxAsList(groups));
    }

    /**
     * Return a new fixed-denomination formatter.  The argument specifies the denomination as
     * the size of the shift from coin-denomination in increasingly-precise decimal places.
     * The returned object will format and parse values according to the default locale, and
     * will format the fractional part of numbers with two decimal places, or fewer as
     * necessary to avoid giving a place to fractional satoshis.
     */
    public static BtcFormat getInstance(int scale) {
        return getInstance(scale, defaultLocale());
    }

    /**
     * Return a new fixed-denomination formatter for the given locale.  The first argument
     * specifies the denomination as the size of the shift from coin-denomination in
     * increasingly-precise decimal places.  The returned object will format and parse values
     * according to the locale specified by the second argument, and will format the fractional
     * part of numbers with two decimal places, or fewer as necessary to avoid giving a place
     * to fractional satoshis.
     */
    public static BtcFormat getInstance(int scale, Locale locale) {
        return getInstance(scale, locale, 2);
    }

    /**
     * Return a new fixed-denomination formatter for the given locale, with the specified
     * fractional decimal placing.  The first argument specifies the denomination as the size
     * of the shift from coin-denomination in increasingly-precise decimal places.  The third
     * parameter is the minimum number of fractional decimal places to use, followed by
     * optional repeating integer parameters each specifying the size of an additional group of
     * fractional decimal places to use as necessary to avoid rounding, down to a maximum
     * precision of satoshis.
     */
    public static BtcFormat getInstance(int scale, Locale locale, int minDecimals, int... groups) {
        return getInstance(scale, locale, minDecimals, boxAsList(groups));
    }

    /**
     * Return a new fixed-denomination formatter for the given locale, with the specified
     * fractional decimal placing.  The first argument specifies the denomination as the size
     * of the shift from coin-denomination in increasingly-precise decimal places.  The third
     * parameter is the minimum number of fractional decimal places to use.  The third argument
     * specifies the minimum number of fractional decimal places in formatted numbers.  The
     * last argument is a {@code List} of {@link Integer} values, each of which
     * specifies the size of an additional group of fractional decimal places to use as
     * necessary to avoid rounding, down to a maximum precision of satoshis.
     */
    public static BtcFormat getInstance(int scale, Locale locale, int minDecimals, List<Integer> groups) {
        return new BtcFixedFormat(locale, scale, minDecimals, groups);
    }

    // ****** FORMATTING *****

    /**
     * Formats a bitcoin monetary value and returns an {@link AttributedCharacterIterator}.
     * By iterating, you can examine what fields apply to each character.  This can be useful
     * since a character may be part of more than one field, for example a grouping separator
     * that is also part of the integer field.
     *
     * @see AttributedCharacterIterator
     */
    @Override
    public AttributedCharacterIterator formatToCharacterIterator(Object obj) { synchronized(numberFormat) {
        DecimalFormatSymbols anteSigns = numberFormat.getDecimalFormatSymbols();
        BigDecimal units = denominateAndRound(inSatoshis(obj), minimumFractionDigits, decimalGroups);
        List<Integer> anteDigits = setFormatterDigits(numberFormat, units.scale(), units.scale());
        AttributedCharacterIterator i = numberFormat.formatToCharacterIterator(units);
        numberFormat.setDecimalFormatSymbols(anteSigns);
        setFormatterDigits(numberFormat, anteDigits.get(0), anteDigits.get(1));
        return i;
    }}

    /**
     * Formats a bitcoin value as a number and possibly a units indicator and appends the
     * resulting text to the given string buffer.  The type of monetary value argument can be
     * any one of any of the following classes: {@link Coin},
     * {@link Integer}, {@link Long}, {@link BigInteger},
     * {@link BigDecimal}.  Numeric types that can represent only an integer are interpreted
     * as that number of satoshis.  The value of a {@link BigDecimal} is interpreted as that
     * number of bitcoins, rounded to the nearest satoshi as necessary.
     *
     * @return the {@link StringBuffer} passed in as {@code toAppendTo}
     */
    @Override
    public StringBuffer format(Object qty, StringBuffer toAppendTo, FieldPosition pos) {
        return format(qty, toAppendTo, pos, minimumFractionDigits, decimalGroups);
    }

    /**
     * Formats a bitcoin value as a number and possibly a units indicator to a
     * {@link String}.The type of monetary value argument can be any one of any of the
     * following classes: {@link Coin}, {@link Integer}, {@link Long},
     * {@link BigInteger}, {@link BigDecimal}.  Numeric types that can represent only
     * an integer are interpreted as that number of satoshis.  The value of a
     * {@link BigDecimal} is interpreted as that number of bitcoins, rounded to the
     * nearest satoshi as necessary.
     *
     * @param minDecimals The minimum number of decimal places in the fractional part of the formatted number
     * @param fractionGroups The sizes of optional additional fractional decimal-place groups
     * @throws IllegalArgumentException if the number of fraction places is negative.
     */
    public String format(Object qty, int minDecimals, int... fractionGroups) {
        return format(qty, new StringBuffer(), new FieldPosition(0), minDecimals, boxAsList(fractionGroups)).toString();
    }

    /**
     * Formats a bitcoin value as a number and possibly a units indicator and appends the
     * resulting text to the given string buffer.  The type of monetary value argument can be
     * any one of any of the following classes: {@link Coin},
     * {@link Integer}, {@link Long}, {@link BigInteger},
     * {@link BigDecimal}.  Numeric types that can represent only an integer are interpreted
     * as that number of satoshis.  The value of a {@link BigDecimal} is interpreted as that
     * number of bitcoins, rounded to the nearest satoshi as necessary.
     *
     * @param minDecimals The minimum number of decimal places in the fractional part of the formatted number
     * @param fractionGroups The sizes of optional additional fractional decimal-place groups
     * @throws IllegalArgumentException if the number of fraction places is negative.
     */
    public StringBuffer format(Object qty, StringBuffer toAppendTo, FieldPosition pos,
                                            int minDecimals, int... fractionGroups) {
        return format(qty, toAppendTo, pos, minDecimals, boxAsList(fractionGroups));
    }

    private StringBuffer format(Object qty, StringBuffer toAppendTo, FieldPosition pos,
                                            int minDecimals, List<Integer> fractionGroups) {
        checkArgument(minDecimals >= 0, () ->
                "there can be no fewer than zero fractional decimal places");
        synchronized (numberFormat) {
            DecimalFormatSymbols anteSigns = numberFormat.getDecimalFormatSymbols();
            BigDecimal denominatedUnitCount = denominateAndRound(inSatoshis(qty), minDecimals, fractionGroups);
            List<Integer> antePlaces =
                setFormatterDigits(numberFormat, denominatedUnitCount.scale(), denominatedUnitCount.scale());
            StringBuffer s = numberFormat.format(denominatedUnitCount, toAppendTo, pos);
            numberFormat.setDecimalFormatSymbols(anteSigns);
            setFormatterDigits(numberFormat, antePlaces.get(0), antePlaces.get(1));
            return s;
        }
    }    

    /**
     * Return the denomination for formatting the given value.  The returned {@code int}
     * is the size of the decimal-place shift between the given Bitcoin-value denominated in
     * bitcoins and that same value as formatted.  A fixed-denomination formatter will ignore
     * the arguments.
     *
     * @param satoshis The number of satoshis having the value for which the shift is calculated
     * @param fractionPlaces The number of decimal places available for displaying the
                             fractional part of the denominated value
     * @return The size of the shift in increasingly-precise decimal places
     */
    protected abstract int scale(BigInteger satoshis, int fractionPlaces);

    /** Return the denomination of this object.  Fixed-denomination formatters will override
     *  with their configured denomination, auto-formatters with coin denomination.  This
     *  determines the interpretation of parsed numbers lacking a units-indicator. */
    protected abstract int scale();

    /**
     * Takes a bitcoin monetary value that the client wants to format and returns the number of
     * denominational units having the equal value, rounded to the appropriate number of
     * decimal places.  Calls the scale() method of the subclass, which may have the
     * side-effect of changing the currency symbol and code of the underlying `NumberFormat`
     * object, therefore only invoke this from a synchronized method that resets the NumberFormat.
     */
    private BigDecimal denominateAndRound(BigInteger satoshis, int minDecimals, List<Integer> fractionGroups) {
        int scale = scale(satoshis, minDecimals);
        BigDecimal denominatedUnitCount = new BigDecimal(satoshis).movePointLeft(offSatoshis(scale));
        int places = calculateFractionPlaces(denominatedUnitCount, scale, minDecimals, fractionGroups);
        return denominatedUnitCount.setScale(places, HALF_UP);
    }

    /** Sets the number of fractional decimal places to be displayed on the given
     *  NumberFormat object to the value of the given integer.
     *  @return The minimum and maximum fractional places settings that the
     *          formatter had before this change, as an unmodifiable List. */
    private static List<Integer> setFormatterDigits(DecimalFormat formatter, int min, int max) {
        List<Integer> ante = Collections.unmodifiableList(
            Arrays.asList(
                formatter.getMinimumFractionDigits(),
                formatter.getMaximumFractionDigits()
            )
        );
        formatter.setMinimumFractionDigits(min);
        formatter.setMaximumFractionDigits(max);
        return ante;
    }

    /** Return the number of fractional decimal places to be displayed when formatting
     *  the given number of monetary units of the denomination indicated by the given decimal scale value,
     *  where 0 = coin, 3 = millicoin, and so on.
     *
     *  @param unitCount      the number of monetary units to be formatted
     *  @param scale          the denomination of those units as the decimal-place shift from coins
     *  @param minDecimals    the minimum number of fractional decimal places
     *  @param fractionGroups the sizes of option fractional decimal-place groups
     */
    private static int calculateFractionPlaces(
        BigDecimal unitCount, int scale, int minDecimals, List<Integer> fractionGroups)
    {
        /* Taking into account BOTH the user's preference for decimal-place groups, AND the prohibition
         * against displaying a fractional number of satoshis, determine the maximum possible number of
         * fractional decimal places. */
        int places = minDecimals;
        for (int group : fractionGroups) { places += group; }
        int max = Math.min(places, offSatoshis(scale));

        places = Math.min(minDecimals,max);
        for (int group : fractionGroups) {
            /* Compare the value formatted using only this many decimal places to the
             * same value using as many places as possible.  If there's no difference, then
             * there's no reason to continue adding more places.  */
            if (unitCount.setScale(places, HALF_UP).compareTo(unitCount.setScale(max, HALF_UP)) == 0) break;
            places += group;
            if (places > max) places = max;
        }
        return places;
    }

    /**
     * Takes an object representing a bitcoin quantity of any type the
     * client is permitted to pass us, and return a BigInteger representing the
     * number of satoshis having the equivalent value. */
    private static BigInteger inSatoshis(Object qty) {
        BigInteger satoshis;
        // the value might be bitcoins or satoshis
        if (qty instanceof Long || qty instanceof Integer)
            satoshis = BigInteger.valueOf(((Number)qty).longValue());
        else if (qty instanceof BigInteger)
            satoshis = (BigInteger)qty;
        else if (qty instanceof BigDecimal)
            satoshis = ((BigDecimal)qty).movePointRight(Coin.SMALLEST_UNIT_EXPONENT).
                       setScale(0,BigDecimal.ROUND_HALF_UP).unscaledValue();
        else if (qty instanceof Coin)
            satoshis = BigInteger.valueOf(((Coin)qty).value);
        else
            throw new IllegalArgumentException("Cannot format a " + qty.getClass().getSimpleName() +
                                               " as a Bicoin value");
        return satoshis;
    }

    // ****** PARSING *****

    /**
      * Parse a {@link String} representation of a Bitcoin monetary value.  Returns a
      * {@link Coin} object that represents the parsed value.
      * @see NumberFormat */
    @Override
    public final Object parseObject(String source, ParsePosition pos) { return parse(source, pos); }

    private static class ScaleMatcher {
        public Pattern pattern;
        public int scale;
        ScaleMatcher(Pattern p, int s) { pattern = p; scale = s; }
    }

    /* Lazy initialization;  No reason to create all these objects unless needed for parsing */
    // coin indicator regex String; TODO: does this need to be volatile?
    private volatile String ci = "(" + COIN_SYMBOL + "|" + COIN_SYMBOL_ALT + "|B⃦|" + COIN_CODE + "|XBT)";
    private Pattern coinPattern;
    private volatile ScaleMatcher[] denoms;
    ScaleMatcher[] denomMatchers() {
        ScaleMatcher[] result = denoms;
        if (result == null) { synchronized(this) {
            result = denoms;
            if (result == null) {
                if (! coinSymbol().matches(ci)) ci = ci.replaceFirst("\\(", "(" + coinSymbol() + "|");
                if (! coinCode().matches(ci))  {
                    ci = ci.replaceFirst("\\)", "|" + coinCode() + ")");
                }
                coinPattern = Pattern.compile(ci + "?");
                result = denoms = new ScaleMatcher[]{
                    new ScaleMatcher(Pattern.compile("¢" + ci + "?|c" + ci), 2), // centi 
                    new ScaleMatcher(Pattern.compile("₥" + ci + "?|m" + ci), MILLICOIN_SCALE),
                    new ScaleMatcher(Pattern.compile("([µu]" + ci + ")"),    MICROCOIN_SCALE),
                    new ScaleMatcher(Pattern.compile("(da" + ci + ")"),     -1), // deka
                    new ScaleMatcher(Pattern.compile("(h" + ci + ")"),      -2), // hekto
                    new ScaleMatcher(Pattern.compile("(k" + ci + ")"),      -3), // kilo
                    new ScaleMatcher(Pattern.compile("(M" + ci + ")"),      -6)  // mega
                };
            }
        }}
        return result;
    }

    /** Set both the currency symbol and international code of the underlying {@link
      * java.text.NumberFormat} object to the value of the given {@link String}.
      * This method is invoked in the process of parsing, not formatting.
      *
      * Only invoke this from code synchronized on the value of the first argument, and don't
      * forget to put the symbols back otherwise equals(), hashCode() and immutability will
      * break.  */
    private static DecimalFormatSymbols setSymbolAndCode(DecimalFormat numberFormat, String sign) {
        return setSymbolAndCode(numberFormat, sign, sign);
    }

    /** Set the currency symbol and international code of the underlying {@link
      * java.text.NumberFormat} object to the values of the last two arguments, respectively.
      * This method is invoked in the process of parsing, not formatting.
      *
      * Only invoke this from code synchronized on value of the first argument, and don't
      * forget to put the symbols back otherwise equals(), hashCode() and immutability will
      * break.  */
    private static DecimalFormatSymbols setSymbolAndCode(DecimalFormat numberFormat, String symbol, String code) {
        checkState(Thread.holdsLock(numberFormat));
        DecimalFormatSymbols fs = numberFormat.getDecimalFormatSymbols();
        DecimalFormatSymbols ante = (DecimalFormatSymbols)fs.clone();
        fs.setInternationalCurrencySymbol(code);
        fs.setCurrencySymbol(symbol);
        numberFormat.setDecimalFormatSymbols(fs);
        return ante;
    }

    /**
     * Set both the currency symbol and code of the underlying, mutable NumberFormat object
     * according to the given denominational units scale factor.  This is for formatting, not parsing.
     *
     * <p>Set back to zero when you're done formatting otherwise immutability, equals() and
     * hashCode() will break!
     *
     * @param scale Number of places the decimal point will be shifted when formatting
     *              a quantity of satoshis.
     */
    protected static void prefixUnitsIndicator(DecimalFormat numberFormat, int scale) {
        checkState(Thread.holdsLock(numberFormat)); // make sure caller intends to reset before changing
        DecimalFormatSymbols fs = numberFormat.getDecimalFormatSymbols();
        setSymbolAndCode(numberFormat,
            prefixSymbol(fs.getCurrencySymbol(), scale), prefixCode(fs.getInternationalCurrencySymbol(), scale)
        );
    }

    /** Parse a {@link String} representation of a Bitcoin monetary value.  If this
     * object's pattern includes a currency sign, either symbol or code, as by default is true
     * for instances of {@link BtcAutoFormat} and false for instances of {@link
     * BtcFixedFormat}, then denominated (i.e., prefixed) currency signs in the parsed String
     * will be recognized, and the parsed number will be interpreted as a quantity of units
     * having that recognized denomination.
     * <p>If the pattern includes a currency sign but no currency sign is detected in the parsed
     * String, then the number is interpreted as a quatity of bitcoins.
     * <p>If the pattern contains neither a currency symbol nor sign, then instances of {@link
     * BtcAutoFormat} will interpret the parsed number as a quantity of bitcoins, and instances
     * of {@link BtcAutoFormat} will interpret the number as a quantity of that instance's
     * configured denomination, which can be ascertained by invoking the {@link
     * BtcFixedFormat#symbol()} or {@link BtcFixedFormat#code()} method.
     *
     * <p>Consider using the single-argument version of this overloaded method unless you need to
     * keep track of the current parse position.
     *
     * @return a Coin object representing the parsed value
     * @see java.text.ParsePosition
     */
    public Coin parse(String source, ParsePosition pos) {
        DecimalFormatSymbols anteSigns = null;
        int parseScale = COIN_SCALE; // default
        Coin coin = null;
        synchronized (numberFormat) {
            if (numberFormat.toPattern().contains("¤")) {
                for(ScaleMatcher d : denomMatchers()) {
                    Matcher matcher = d.pattern.matcher(source);
                    if (matcher.find()) {
                        anteSigns = setSymbolAndCode(numberFormat, matcher.group());
                        parseScale = d.scale;
                        break;
                    }
                }
                if (parseScale == COIN_SCALE) {
                    Matcher matcher = coinPattern.matcher(source);
                    matcher.find();
                    anteSigns = setSymbolAndCode(numberFormat, matcher.group());
                }
            } else parseScale = scale();

            Number number = numberFormat.parse(source, pos);
            if (number != null) try {
                    coin = Coin.valueOf(
                        ((BigDecimal)number).movePointRight(offSatoshis(parseScale)).setScale(0, HALF_UP).longValue()
                    );
                } catch (IllegalArgumentException e) {
                    pos.setIndex(0);
                }
            if (anteSigns != null) numberFormat.setDecimalFormatSymbols(anteSigns);
        }
        return coin;
    }

    /** Parse a {@link String} representation of a Bitcoin monetary value.  If this
     * object's pattern includes a currency sign, either symbol or code, as by default is true
     * for instances of {@link BtcAutoFormat} and false for instances of {@link
     * BtcFixedFormat}, then denominated (i.e., prefixed) currency signs in the parsed String
     * will be recognized, and the parsed number will be interpreted as a quantity of units
     * having that recognized denomination.
     * <p>If the pattern includes a currency sign but no currency sign is detected in the parsed
     * String, then the number is interpreted as a quatity of bitcoins.
     * <p>If the pattern contains neither a currency symbol nor sign, then instances of {@link
     * BtcAutoFormat} will interpret the parsed number as a quantity of bitcoins, and instances
     * of {@link BtcAutoFormat} will interpret the number as a quantity of that instance's
     * configured denomination, which can be ascertained by invoking the {@link
     * BtcFixedFormat#symbol()} or {@link BtcFixedFormat#code()} method.
     *
     * @return a Coin object representing the parsed value
     */
    public Coin parse(String source) throws ParseException {
        return (Coin)parseObject(source);
    }

    /****** END OF PARSING STUFF *****/

    protected static String prefixCode(String code, int scale) {
        switch (scale) {
        case COIN_SCALE:      return code;
        case 1:               return "d" + code;
        case 2:               return "c" + code;
        case MILLICOIN_SCALE: return "m" + code;
        case MICROCOIN_SCALE: return "µ" + code;
        case -1:              return "da" + code;
        case -2:              return "h" + code;
        case -3:              return "k" + code;
        case -6:              return "M" + code;
        default: throw new IllegalStateException("No known prefix for scale " + String.valueOf(scale));
        }
    }

    protected static String prefixSymbol(String symbol, int scale) {
        switch (scale) {
        case COIN_SCALE:      return symbol;
        case 1:               return "d" + symbol;
        case 2:               return "¢" + symbol;
        case MILLICOIN_SCALE: return "₥" + symbol;
        case MICROCOIN_SCALE: return "µ" + symbol;
        case -1:              return "da" + symbol;
        case -2:              return "h" + symbol;
        case -3:              return "k" + symbol;
        case -6:              return "M" + symbol;
        default: throw new IllegalStateException("No known prefix for scale " + String.valueOf(scale));
        }
    }

    /** Guarantee a formatting pattern has a subpattern for negative values.  This method takes
     *  a pattern that may be missing a negative subpattern, and returns the same pattern with
     *  a negative subpattern appended as needed.
     *
     *  <p>This method accommodates an imperfection in the Java formatting code and distributed
     *  locale data.  To wit: the subpattern for negative numbers is optional and not all
     *  locales have one. In those cases, {@link DecimalFormat} will indicate numbers
     *  less than zero by adding a negative sign as the first character of the prefix of the
     *  positive subpattern.
     *
     *  <p>We don't like this, since we claim the negative sign applies to the number not the
     *  units, and therefore it ought to be adjacent to the number, displacing the
     *  currency-units indicator if necessary.
     */
    protected static String negify(String pattern) {
        if (pattern.contains(";")) return pattern;
        else {
            if (pattern.contains("-"))
                throw new IllegalStateException("Positive pattern contains negative sign");
            // the regex matches everything until the first non-quoted number character
            return pattern + ";" + pattern.replaceFirst("^([^#0,.']*('[^']*')?)*", "$0-");
        }
    }

    /**
     * Return an array of all locales for which the getInstance() method of this class can
     * return localized instances.  See {@link NumberFormat#getAvailableLocales()}
     */
    public static Locale[] getAvailableLocales() { return NumberFormat.getAvailableLocales(); }

    /** Return the unprefixed currency symbol for bitcoins configured for this object.  The
     *  return value of this method is constant throughout the life of an instance.  */
    public String coinSymbol() { synchronized(numberFormat) {
        return numberFormat.getDecimalFormatSymbols().getCurrencySymbol();
    }}

    /** Return the unprefixed international currency code for bitcoins configured for this
     * object.  The return value of this method is constant throughough the life of an instance.  */
    public String coinCode() { synchronized(numberFormat) {
        return numberFormat.getDecimalFormatSymbols().getInternationalCurrencySymbol();
    }}

    /** Return a representation of the pattern used by this instance for formatting and
     *  parsing.  The format is similar to, but not the same as the format recognized by the
     *  {@link Builder#pattern} and {@link Builder#localizedPattern} methods.  The pattern
     *  returned by this method is localized, any currency signs expressed are literally, and
     *  optional fractional decimal places are shown grouped in parentheses. */
    public String pattern() { synchronized(numberFormat) {
        StringBuilder groups = new StringBuilder();
        for (int group : decimalGroups) {
            groups.append("(").append(Strings.repeat("#", group)).append(")");
        }
        DecimalFormatSymbols s = numberFormat.getDecimalFormatSymbols();
        String digit = String.valueOf(s.getDigit());
        String exp = s.getExponentSeparator();
        String groupSep = String.valueOf(s.getGroupingSeparator());
        String moneySep = String.valueOf(s.getMonetaryDecimalSeparator());
        String zero = String.valueOf(s.getZeroDigit());
        String boundary = String.valueOf(s.getPatternSeparator());
        String minus = String.valueOf(s.getMinusSign());
        String decSep = String.valueOf(s.getDecimalSeparator());

        String prefixAndNumber = "(^|" + boundary+ ")" +
            "([^" + Matcher.quoteReplacement(digit + zero + groupSep + decSep + moneySep) + "']*('[^']*')?)*" +
            "[" + Matcher.quoteReplacement(digit + zero + groupSep + decSep + moneySep + exp) + "]+";

        return numberFormat.toLocalizedPattern().
            replaceAll(prefixAndNumber, "$0" + groups.toString()).
               replaceAll("¤¤", Matcher.quoteReplacement(coinCode())).
               replaceAll("¤", Matcher.quoteReplacement(coinSymbol()));
    }}

    /** Return a copy of the localized symbols used by this instance for formatting and parsing.  */
    public DecimalFormatSymbols symbols() { synchronized(numberFormat) {
        return numberFormat.getDecimalFormatSymbols();
    }}

    /** Return true if the given object is equivalent to this one.
      * Formatters for different locales will never be equal, even
      * if they behave identically. */
    @Override public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof BtcFormat)) return false;
        BtcFormat other = (BtcFormat)o;
        return other.pattern().equals(pattern()) &&
               other.symbols().equals(symbols()) &&
               other.minimumFractionDigits == minimumFractionDigits;
    }

    /** Return a hash code value for this instance.
     *  @see java.lang.Object#hashCode
     */
    @Override public int hashCode() {
        return Objects.hash(pattern(), symbols(), minimumFractionDigits, decimalGroups);
    }

}
