// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.dev.impl.number;

import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
import java.text.FieldPosition;

import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.text.PluralRules;
import com.ibm.icu.text.PluralRules.Operand;
import com.ibm.icu.text.UFieldPosition;

/**
 * This is an older implementation of DecimalQuantity. A newer, faster implementation is
 * DecimalQuantity2. I kept this implementation around because it was useful for testing purposes
 * (being able to compare the output of one implementation with the other).
 *
 * <p>This class is NOT IMMUTABLE and NOT THREAD SAFE and is intended to be used by a single thread
 * to format a number through a formatter, which is thread-safe.
 */
public class DecimalQuantity_SimpleStorage implements DecimalQuantity {
  // Four positions: left optional '(', left required '[', right required ']', right optional ')'.
  // These four positions determine which digits are displayed in the output string.  They do NOT
  // affect rounding.  These positions are internal-only and can be specified only by the public
  // endpoints like setFractionLength, setIntegerLength, and setSignificantDigits, among others.
  //
  //   * Digits between lReqPos and rReqPos are in the "required zone" and are always displayed.
  //   * Digits between lOptPos and rOptPos but outside the required zone are in the "optional zone"
  //     and are displayed unless they are trailing off the left or right edge of the number and
  //     have a numerical value of zero.  In order to be "trailing", the digits need to be beyond
  //     the decimal point in their respective directions.
  //   * Digits outside of the "optional zone" are never displayed.
  //
  // See the table below for illustrative examples.
  //
  // +---------+---------+---------+---------+------------+------------------------+--------------+
  // | lOptPos | lReqPos | rReqPos | rOptPos |   number   |        positions       | en-US string |
  // +---------+---------+---------+---------+------------+------------------------+--------------+
  // |    5    |    2    |   -1    |   -5    |   1234.567 |     ( 12[34.5]67  )    |   1,234.567  |
  // |    3    |    2    |   -1    |   -5    |   1234.567 |      1(2[34.5]67  )    |     234.567  |
  // |    3    |    2    |   -1    |   -2    |   1234.567 |      1(2[34.5]6)7      |     234.56   |
  // |    6    |    4    |    2    |   -5    | 123456789. |  123(45[67]89.     )   | 456,789.     |
  // |    6    |    4    |    2    |    1    | 123456789. |     123(45[67]8)9.     | 456,780.     |
  // |   -1    |   -1    |   -3    |   -4    | 0.123456   |     0.1([23]4)56       |        .0234 |
  // |    6    |    4    |   -2    |   -2    |     12.3   |     (  [  12.3 ])      |    0012.30   |
  // +---------+---------+---------+---------+------------+------------------------+--------------+
  //
  private int lOptPos = Integer.MAX_VALUE;
  private int lReqPos = 0;
  private int rReqPos = 0;
  private int rOptPos = Integer.MIN_VALUE;

  // Internally, attempt to use a long to store the number. A long can hold numbers between 18 and
  // 19 digits, covering the vast majority of use cases. We store three values: the long itself,
  // the "scale" of the long (the power of 10 represented by the rightmost digit in the long), and
  // the "precision" (the number of digits in the long). "primary" and "primaryScale" are the only
  // two variables that are required for representing the number in memory. "primaryPrecision" is
  // saved only for the sake of performance enhancements when performing certain operations. It can
  // always be re-computed from "primary" and "primaryScale".
  private long primary;
  private int primaryScale;
  private int primaryPrecision;

  // If the decimal can't fit into the long, fall back to a BigDecimal.
  private BigDecimal fallback;

  // Other properties
  private int flags;
  private static final int NEGATIVE_FLAG = 1;
  private static final int INFINITY_FLAG = 2;
  private static final int NAN_FLAG = 4;
  private static final long[] POWERS_OF_TEN = {
    1L,
    10L,
    100L,
    1000L,
    10000L,
    100000L,
    1000000L,
    10000000L,
    100000000L,
    1000000000L,
    10000000000L,
    100000000000L,
    1000000000000L,
    10000000000000L,
    100000000000000L,
    1000000000000000L,
    10000000000000000L,
    100000000000000000L,
    1000000000000000000L
  };

  @Override
  public int maxRepresentableDigits() {
    return Integer.MAX_VALUE;
  }

  public DecimalQuantity_SimpleStorage(long input) {
    if (input < 0) {
      setNegative(true);
      input *= -1;
    }

    primary = input;
    primaryScale = 0;
    primaryPrecision = computePrecision(primary);
    fallback = null;
  }

  /**
   * Creates a DecimalQuantity from the given double value. Internally attempts several strategies
   * for converting the double to an exact representation, falling back on a BigDecimal if it fails
   * to do so.
   *
   * @param input The double to represent by this DecimalQuantity.
   */
  public DecimalQuantity_SimpleStorage(double input) {
    if (input < 0) {
      setNegative(true);
      input *= -1;
    }

    // First try reading from IEEE bits. This is trivial only for doubles in [2^52, 2^64). If it
    // fails, we wasted only a few CPU cycles.
    long ieeeBits = Double.doubleToLongBits(input);
    int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
    if (exponent >= 52 && exponent <= 63) {
      // We can convert this double directly to a long.
      long mantissa = (ieeeBits & 0x000fffffffffffffL) + 0x0010000000000000L;
      primary = (mantissa << (exponent - 52));
      primaryScale = 0;
      primaryPrecision = computePrecision(primary);
      return;
    }

    // Now try parsing the string produced by Double.toString().
    String temp = Double.toString(input);
    try {
      if (temp.length() == 3 && temp.equals("0.0")) {
        // Case 1: Zero.
        primary = 0L;
        primaryScale = 0;
        primaryPrecision = 0;
      } else if (temp.indexOf('E') != -1) {
        // Case 2: Exponential notation.
        assert temp.indexOf('.') == 1;
        int expPos = temp.indexOf('E');
        primary = Long.parseLong(temp.charAt(0) + temp.substring(2, expPos));
        primaryScale = Integer.parseInt(temp.substring(expPos + 1)) - (expPos - 1) + 1;
        primaryPrecision = expPos - 1;
      } else if (temp.charAt(0) == '0') {
        // Case 3: Fraction-only number.
        assert temp.indexOf('.') == 1;
        primary = Long.parseLong(temp.substring(2)); // ignores leading zeros
        primaryScale = 2 - temp.length();
        primaryPrecision = computePrecision(primary);
      } else if (temp.charAt(temp.length() - 1) == '0') {
        // Case 4: Integer-only number.
        assert temp.indexOf('.') == temp.length() - 2;
        int rightmostNonzeroDigitIndex = temp.length() - 3;
        while (temp.charAt(rightmostNonzeroDigitIndex) == '0') {
          rightmostNonzeroDigitIndex -= 1;
        }
        primary = Long.parseLong(temp.substring(0, rightmostNonzeroDigitIndex + 1));
        primaryScale = temp.length() - rightmostNonzeroDigitIndex - 3;
        primaryPrecision = rightmostNonzeroDigitIndex + 1;
      } else if (temp.equals("Infinity")) {
        // Case 5: Infinity.
        primary = 0;
        setInfinity(true);
      } else if (temp.equals("NaN")) {
        // Case 6: NaN.
        primary = 0;
        setNaN(true);
      } else {
        // Case 7: Number with both a fraction and an integer.
        int decimalPos = temp.indexOf('.');
        primary = Long.parseLong(temp.substring(0, decimalPos) + temp.substring(decimalPos + 1));
        primaryScale = decimalPos - temp.length() + 1;
        primaryPrecision = temp.length() - 1;
      }
    } catch (NumberFormatException e) {
      // The digits of the double can't fit into the long.
      primary = -1;
      fallback = new BigDecimal(temp);
    }
  }

  static final double LOG_2_OF_TEN = 3.32192809489;

  public DecimalQuantity_SimpleStorage(double input, boolean fast) {
    if (input < 0) {
      setNegative(true);
      input *= -1;
    }

    // Our strategy is to read all digits that are *guaranteed* to be valid without delving into
    // the IEEE rounding rules.  This strategy might not end up with a perfect representation of
    // the fractional part of the double.
    long ieeeBits = Double.doubleToLongBits(input);
    int exponent = (int) ((ieeeBits & 0x7ff0000000000000L) >> 52) - 0x3ff;
    long mantissa = (ieeeBits & 0x000fffffffffffffL) + 0x0010000000000000L;
    if (exponent > 63) {
      throw new IllegalArgumentException(); // FIXME
    } else if (exponent >= 52) {
      primary = (mantissa << (exponent - 52));
      primaryScale = 0;
      primaryPrecision = computePrecision(primary);
      return;
    } else if (exponent >= 0) {
      int shift = 52 - exponent;
      primary = (mantissa >> shift); // integer part
      int fractionCount = (int) (shift / LOG_2_OF_TEN);
      long fraction = (mantissa - (primary << shift)) + 1L; // TODO: Explain the +1L
      primary *= POWERS_OF_TEN[fractionCount];
      for (int i = 0; i < fractionCount; i++) {
        long times10 = (fraction * 10L);
        long digit = times10 >> shift;
        assert digit >= 0 && digit < 10;
        primary += digit * POWERS_OF_TEN[fractionCount - i - 1];
        fraction = times10 & ((1L << shift) - 1);
      }
      primaryScale = -fractionCount;
      primaryPrecision = computePrecision(primary);
    } else {
      throw new IllegalArgumentException(); // FIXME
    }
  }

  public DecimalQuantity_SimpleStorage(BigDecimal decimal) {
    setToBigDecimal(decimal);
  }

  public DecimalQuantity_SimpleStorage(DecimalQuantity_SimpleStorage other) {
    copyFrom(other);
  }

  @Override
  public void setToBigDecimal(BigDecimal decimal) {
    if (decimal.compareTo(BigDecimal.ZERO) < 0) {
      setNegative(true);
      decimal = decimal.negate();
    }

    primary = -1;
    if (decimal.compareTo(BigDecimal.ZERO) == 0) {
      fallback = BigDecimal.ZERO;
    } else {
      fallback = decimal;
    }
  }

  @Override
  public DecimalQuantity_SimpleStorage createCopy() {
    return new DecimalQuantity_SimpleStorage(this);
  }

  /**
   * Make the internal state of this DecimalQuantity equal to another DecimalQuantity.
   *
   * @param other The template DecimalQuantity. All properties from this DecimalQuantity will be
   *     copied into this DecimalQuantity.
   */
  @Override
  public void copyFrom(DecimalQuantity other) {
    // TODO: Check before casting
    DecimalQuantity_SimpleStorage _other = (DecimalQuantity_SimpleStorage) other;
    lOptPos = _other.lOptPos;
    lReqPos = _other.lReqPos;
    rReqPos = _other.rReqPos;
    rOptPos = _other.rOptPos;
    primary = _other.primary;
    primaryScale = _other.primaryScale;
    primaryPrecision = _other.primaryPrecision;
    fallback = _other.fallback;
    flags = _other.flags;
  }

  @Override
  public long getPositionFingerprint() {
    long fingerprint = 0;
    fingerprint ^= lOptPos;
    fingerprint ^= (lReqPos << 16);
    fingerprint ^= ((long) rReqPos << 32);
    fingerprint ^= ((long) rOptPos << 48);
    return fingerprint;
  }

  /**
   * Utility method to compute the number of digits ("precision") in a long.
   *
   * @param input The long (which can't contain more than 19 digits).
   * @return The precision of the long.
   */
  private static int computePrecision(long input) {
    int precision = 0;
    while (input > 0) {
      input /= 10;
      precision++;
    }
    return precision;
  }

  /**
   * Changes the internal representation from a long to a BigDecimal. Used only for operations that
   * don't support longs.
   */
  private void convertToBigDecimal() {
    if (primary == -1) {
      return;
    }

    fallback = new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
    primary = -1;
  }

  @Override
  public void setMinInteger(int minInt) {
    // Graceful failures for bogus input
    minInt = Math.max(0, minInt);

    // Save values into internal state
    // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
    lReqPos = minInt;
  }

  @Override
  public void setMinFraction(int minFrac) {
    // Graceful failures for bogus input
    minFrac = Math.max(0, minFrac);

    // Save values into internal state
    // Negation is safe for minFrac/maxFrac because -Integer.MAX_VALUE > Integer.MIN_VALUE
    rReqPos = -minFrac;
  }

  @Override
  public void applyMaxInteger(int maxInt) {
    BigDecimal d;
    if (primary != -1) {
      d = BigDecimal.valueOf(primary).scaleByPowerOfTen(primaryScale);
    } else {
      d = fallback;
    }
    d = d.scaleByPowerOfTen(-maxInt).remainder(BigDecimal.ONE).scaleByPowerOfTen(maxInt);
    if (primary != -1) {
      primary = d.scaleByPowerOfTen(-primaryScale).longValueExact();
    } else {
      fallback = d;
    }
  }

  @Override
  public void roundToIncrement(BigDecimal roundingInterval, MathContext mathContext) {
    BigDecimal d =
        (primary == -1) ? fallback : new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
    if (isNegative()) d = d.negate();
    d = d.divide(roundingInterval, 0, mathContext.getRoundingMode()).multiply(roundingInterval);
    if (isNegative()) d = d.negate();
    fallback = d;
    primary = -1;
  }

  @Override
  public void roundToNickel(int roundingMagnitude, MathContext mathContext) {
    BigDecimal nickel = BigDecimal.valueOf(5).scaleByPowerOfTen(roundingMagnitude);
    roundToIncrement(nickel, mathContext);
  }

  @Override
  public void roundToMagnitude(int roundingMagnitude, MathContext mathContext) {
    if (roundingMagnitude < -1000) {
      roundToInfinity();
      return;
    }
    if (primary == -1) {
      if (isNegative()) fallback = fallback.negate();
      fallback = fallback.setScale(-roundingMagnitude, mathContext.getRoundingMode());
      if (isNegative()) fallback = fallback.negate();
      // Enforce the math context.
      fallback = fallback.round(mathContext);
    } else {
      int relativeScale = primaryScale - roundingMagnitude;
      if (relativeScale < -18) {
        // No digits will remain after rounding the number.
        primary = 0L;
        primaryScale = roundingMagnitude;
        primaryPrecision = 0;
      } else if (relativeScale < 0) {
        // This is the harder case, when we need to perform the rounding logic.
        // First check if the rightmost digits are already zero, where we can skip rounding.
        if ((primary % POWERS_OF_TEN[0 - relativeScale]) == 0) {
          // No rounding is necessary.
        } else {
          // TODO: Make this more efficient. Temporarily, convert to a BigDecimal and back again.
          BigDecimal temp = new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
          if (isNegative()) temp = temp.negate();
          temp = temp.setScale(-roundingMagnitude, mathContext.getRoundingMode());
          if (isNegative()) temp = temp.negate();
          temp = temp.scaleByPowerOfTen(-roundingMagnitude);
          primary = temp.longValueExact(); // should never throw
          primaryScale = roundingMagnitude;
          primaryPrecision = computePrecision(primary);
        }
      } else {
        // No rounding is necessary. All digits are to the left of the rounding magnitude.
      }
      // Enforce the math context.
      primary = new BigDecimal(primary).round(mathContext).longValueExact();
      primaryPrecision = computePrecision(primary);
    }
  }

  @Override
  public void roundToInfinity() {
    // noop
  }

  /**
   * Multiply the internal number by the specified multiplicand. This method forces the internal
   * representation into a BigDecimal. If you are multiplying by a power of 10, use {@link
   * #adjustMagnitude} instead.
   *
   * @param multiplicand The number to be passed to {@link BigDecimal#multiply}.
   */
  @Override
  public void multiplyBy(BigDecimal multiplicand) {
    convertToBigDecimal();
    fallback = fallback.multiply(multiplicand);
    if (fallback.compareTo(BigDecimal.ZERO) < 0) {
      setNegative(!isNegative());
      fallback = fallback.negate();
    }
  }

  @Override
  public void negate() {
    flags ^= NEGATIVE_FLAG;
  }

  /**
   * Divide the internal number by the specified quotient. This method forces the internal
   * representation into a BigDecimal. If you are dividing by a power of 10, use {@link
   * #adjustMagnitude} instead.
   *
   * @param divisor The number to be passed to {@link BigDecimal#divide}.
   * @param scale The scale of the final rounded number. More negative means more decimal places.
   * @param mathContext The math context to use if rounding is necessary.
   */
  @SuppressWarnings("unused")
  private void divideBy(BigDecimal divisor, int scale, MathContext mathContext) {
    convertToBigDecimal();
    // Negate the scale because BigDecimal's scale is defined as the inverse of our scale
    fallback = fallback.divide(divisor, -scale, mathContext.getRoundingMode());
    if (fallback.compareTo(BigDecimal.ZERO) < 0) {
      setNegative(!isNegative());
      fallback = fallback.negate();
    }
  }

  @Override
  public boolean isZeroish() {
    if (primary == -1) {
      return fallback.compareTo(BigDecimal.ZERO) == 0;
    } else {
      return primary == 0;
    }
  }

  /** @return The power of ten of the highest digit represented by this DecimalQuantity */
  @Override
  public int getMagnitude() throws ArithmeticException {
    int scale = (primary == -1) ? scaleBigDecimal(fallback) : primaryScale;
    int precision = (primary == -1) ? precisionBigDecimal(fallback) : primaryPrecision;
    if (precision == 0) {
      throw new ArithmeticException("Magnitude is not well-defined for zero");
    } else {
      return scale + precision - 1;
    }
  }

  /**
   * Changes the magnitude of this DecimalQuantity. If the indices of the represented digits had been
   * previously specified, those indices are moved relative to the DecimalQuantity.
   *
   * <p>This method does NOT perform rounding.
   *
   * @param delta The number of powers of ten to shift (positive shifts to the left).
   */
  @Override
  public void adjustMagnitude(int delta) {
    if (primary == -1) {
      fallback = fallback.scaleByPowerOfTen(delta);
    } else {
      primaryScale = addOrMaxValue(primaryScale, delta);
    }
  }

  private static int addOrMaxValue(int a, int b) {
    // Check for overflow, and return min/max value if overflow occurs.
    if (b < 0 && a + b > a) {
      return Integer.MIN_VALUE;
    } else if (b > 0 && a + b < a) {
      return Integer.MAX_VALUE;
    }
    return a + b;
  }

  /** @return If the number represented by this DecimalQuantity is less than zero */
  @Override
  public boolean isNegative() {
    return (flags & NEGATIVE_FLAG) != 0;
  }

  @Override
  public int signum() {
      return isNegative() ? -1 : isZeroish() ? 0 : 1;
  }

  private void setNegative(boolean isNegative) {
    flags = (flags & (~NEGATIVE_FLAG)) | (isNegative ? NEGATIVE_FLAG : 0);
  }

  @Override
  public boolean isInfinite() {
    return (flags & INFINITY_FLAG) != 0;
  }

  private void setInfinity(boolean isInfinity) {
    flags = (flags & (~INFINITY_FLAG)) | (isInfinity ? INFINITY_FLAG : 0);
  }

  @Override
  public boolean isNaN() {
    return (flags & NAN_FLAG) != 0;
  }

  private void setNaN(boolean isNaN) {
    flags = (flags & (~NAN_FLAG)) | (isNaN ? NAN_FLAG : 0);
  }

  /**
   * Returns a representation of this DecimalQuantity as a double, with possible loss of information.
   */
  @Override
  public double toDouble() {
    double result;
    if (primary == -1) {
      result = fallback.doubleValue();
    } else {
      // TODO: Make this more efficient
      result = primary;
      for (int i = 0; i < primaryScale; i++) {
        result *= 10.;
      }
      for (int i = 0; i > primaryScale; i--) {
        result /= 10.;
      }
    }
    return isNegative() ? -result : result;
  }

  @Override
  public BigDecimal toBigDecimal() {
    BigDecimal result;
    if (primary != -1) {
      result = new BigDecimal(primary).scaleByPowerOfTen(primaryScale);
    } else {
      result = fallback;
    }
    return isNegative() ? result.negate() : result;
  }

  @Override
  public StandardPlural getStandardPlural(PluralRules rules) {
    if (rules == null) {
      // Fail gracefully if the user didn't provide a PluralRules
      return StandardPlural.OTHER;
    } else {
      // TODO: Avoid converting to a double for the sake of PluralRules
      String ruleString = rules.select(toDouble());
      return StandardPlural.orOtherFromString(ruleString);
    }
  }

  @Override
  public double getPluralOperand(Operand operand) {
    // TODO: This is a temporary hack.
    return new PluralRules.FixedDecimal(toDouble()).getPluralOperand(operand);
  }

  public boolean hasNextFraction() {
    if (rReqPos < 0) {
      // We are in the required zone.
      return true;
    } else if (rOptPos >= 0) {
      // We are in the forbidden zone.
      return false;
    } else {
      // We are in the optional zone.
      if (primary == -1) {
        return fallback.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) > 0;
      } else {
        if (primaryScale <= -19) {
          // The number is a fraction so small that it consists of only fraction digits.
          return primary > 0;
        } else if (primaryScale < 0) {
          // Check if we have a fraction part.
          long factor = POWERS_OF_TEN[0 - primaryScale];
          return ((primary % factor) != 0);
        } else {
          // The lowest digit in the long has magnitude greater than -1.
          return false;
        }
      }
    }
  }

  public byte nextFraction() {
    byte returnValue;
    if (primary == -1) {
      BigDecimal temp = fallback.multiply(BigDecimal.TEN);
      returnValue = temp.setScale(0, RoundingMode.FLOOR).remainder(BigDecimal.TEN).byteValue();
      fallback = fallback.setScale(0, RoundingMode.FLOOR).add(temp.remainder(BigDecimal.ONE));
    } else {
      if (primaryScale <= -20) {
        // The number is a fraction so small that it has no first fraction digit.
        primaryScale += 1;
        returnValue = 0;
      } else if (primaryScale < 0) {
        // Extract the fraction digit out of the middle of the long.
        long factor = POWERS_OF_TEN[0 - primaryScale - 1];
        long temp1 = primary / factor;
        long temp2 = primary % factor;
        returnValue = (byte) (temp1 % 10); // not necessarily nonzero
        primary = ((temp1 / 10) * factor) + temp2;
        primaryScale += 1;
        if (temp1 != 0) {
          primaryPrecision -= 1;
        }
      } else {
        // The lowest digit in the long has magnitude greater than -1.
        returnValue = 0;
      }
    }

    // Update digit brackets
    if (lOptPos < 0) {
      lOptPos += 1;
    }
    if (lReqPos < 0) {
      lReqPos += 1;
    }
    if (rReqPos < 0) {
      rReqPos += 1;
    }
    if (rOptPos < 0) {
      rOptPos += 1;
    }

    assert returnValue >= 0;
    return returnValue;
  }

  public boolean hasNextInteger() {
    if (lReqPos > 0) {
      // We are in the required zone.
      return true;
    } else if (lOptPos <= 0) {
      // We are in the forbidden zone.
      return false;
    } else {
      // We are in the optional zone.
      if (primary == -1) {
        return fallback.setScale(0, RoundingMode.FLOOR).compareTo(BigDecimal.ZERO) > 0;
      } else {
        if (primaryScale < -18) {
          // The number is a fraction so small that it has no integer part.
          return false;
        } else if (primaryScale < 0) {
          // Check if we have an integer part.
          long factor = POWERS_OF_TEN[0 - primaryScale];
          return ((primary % factor) != primary); // equivalent: ((primary / 10) != 0)
        } else {
          // The lowest digit in the long has magnitude of at least 0.
          return primary != 0;
        }
      }
    }
  }

  private int integerCount() {
    int digitsRemaining;
    if (primary == -1) {
      digitsRemaining = precisionBigDecimal(fallback) + scaleBigDecimal(fallback);
    } else {
      digitsRemaining = primaryPrecision + primaryScale;
    }
    return Math.min(Math.max(digitsRemaining, lReqPos), lOptPos);
  }

  private int fractionCount() {
    // TODO: This is temporary.
    DecimalQuantity_SimpleStorage copy = new DecimalQuantity_SimpleStorage(this);
    int fractionCount = 0;
    while (copy.hasNextFraction()) {
      copy.nextFraction();
      fractionCount++;
    }
    return fractionCount;
  }

  @Override
  public int getUpperDisplayMagnitude() {
    return integerCount() - 1;
  }

  @Override
  public int getLowerDisplayMagnitude() {
    return -fractionCount();
  }

  //  @Override
  //  public byte getIntegerDigit(int index) {
  //    return getDigitPos(index);
  //  }
  //
  //  @Override
  //  public byte getFractionDigit(int index) {
  //    return getDigitPos(-index - 1);
  //  }

  @Override
  public byte getDigit(int magnitude) {
    // TODO: This is temporary.
    DecimalQuantity_SimpleStorage copy = new DecimalQuantity_SimpleStorage(this);
    if (magnitude < 0) {
      for (int p = -1; p > magnitude; p--) {
        copy.nextFraction();
      }
      return copy.nextFraction();
    } else {
      for (int p = 0; p < magnitude; p++) {
        copy.nextInteger();
      }
      return copy.nextInteger();
    }
  }

  public byte nextInteger() {
    byte returnValue;
    if (primary == -1) {
      returnValue = fallback.setScale(0, RoundingMode.FLOOR).remainder(BigDecimal.TEN).byteValue();
      BigDecimal temp = fallback.divide(BigDecimal.TEN).setScale(0, RoundingMode.FLOOR);
      fallback = fallback.remainder(BigDecimal.ONE).add(temp);
    } else {
      if (primaryScale < -18) {
        // The number is a fraction so small that it has no integer part.
        returnValue = 0;
      } else if (primaryScale < 0) {
        // Extract the integer digit out of the middle of the long. In many ways, this is the heart
        // of the digit iterator algorithm.
        long factor = POWERS_OF_TEN[0 - primaryScale];
        if ((primary % factor) != primary) { // equivalent: ((primary / 10) != 0)
          returnValue = (byte) ((primary / factor) % 10);
          long temp = (primary / 10);
          primary = temp - (temp % factor) + (primary % factor);
          primaryPrecision -= 1;
        } else {
          returnValue = 0;
        }
      } else if (primaryScale == 0) {
        // Fast-path for primaryScale == 0 (otherwise equivalent to previous step).
        if (primary != 0) {
          returnValue = (byte) (primary % 10);
          primary /= 10;
          primaryPrecision -= 1;
        } else {
          returnValue = 0;
        }
      } else {
        // The lowest digit in the long has magnitude greater than 0.
        primaryScale -= 1;
        returnValue = 0;
      }
    }

    // Update digit brackets
    if (lOptPos > 0) {
      lOptPos -= 1;
    }
    if (lReqPos > 0) {
      lReqPos -= 1;
    }
    if (rReqPos > 0) {
      rReqPos -= 1;
    }
    if (rOptPos > 0) {
      rOptPos -= 1;
    }

    assert returnValue >= 0;
    return returnValue;
  }

  /**
   * Helper method to compute the precision of a BigDecimal by our definition of precision, which is
   * that the number zero gets precision zero.
   *
   * @param decimal The BigDecimal whose precision to compute.
   * @return The precision by our definition.
   */
  private static int precisionBigDecimal(BigDecimal decimal) {
    if (decimal.compareTo(BigDecimal.ZERO) == 0) {
      return 0;
    } else {
      return decimal.precision();
    }
  }

  /**
   * Helper method to compute the scale of a BigDecimal by our definition of scale, which is that
   * deeper fractions result in negative scales as opposed to positive scales.
   *
   * @param decimal The BigDecimal whose scale to compute.
   * @return The scale by our definition.
   */
  private static int scaleBigDecimal(BigDecimal decimal) {
    return -decimal.scale();
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("<DecimalQuantity1 ");
    if (primary == -1) {
      sb.append(lOptPos > 1000 ? "max" : lOptPos);
      sb.append(":");
      sb.append(lReqPos);
      sb.append(":");
      sb.append(rReqPos);
      sb.append(":");
      sb.append(rOptPos < -1000 ? "min" : rOptPos);
      sb.append(" ");
      sb.append(fallback.toString());
    } else {
      String digits = Long.toString(primary);
      int iDec = digits.length() + primaryScale;
      int iLP = iDec - toRange(lOptPos, -1000, 1000);
      int iLB = iDec - toRange(lReqPos, -1000, 1000);
      int iRB = iDec - toRange(rReqPos, -1000, 1000);
      int iRP = iDec - toRange(rOptPos, -1000, 1000);
      iDec = Math.max(Math.min(iDec, digits.length() + 1), -1);
      iLP = Math.max(Math.min(iLP, digits.length() + 1), -1);
      iLB = Math.max(Math.min(iLB, digits.length() + 1), -1);
      iRB = Math.max(Math.min(iRB, digits.length() + 1), -1);
      iRP = Math.max(Math.min(iRP, digits.length() + 1), -1);

      for (int i = -1; i <= digits.length() + 1; i++) {
        if (i == iLP) sb.append('(');
        if (i == iLB) sb.append('[');
        if (i == iDec) sb.append('.');
        if (i == iRB) sb.append(']');
        if (i == iRP) sb.append(')');
        if (i >= 0 && i < digits.length()) sb.append(digits.charAt(i));
        else sb.append('\u00A0');
      }
    }
    sb.append(">");
    return sb.toString();
  }

  @Override
  public String toPlainString() {
      // NOTE: This logic is duplicated between here and DecimalQuantity_AbstractBCD.
      StringBuilder sb = new StringBuilder();
      if (isNegative()) {
          sb.append('-');
      }
      for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
        sb.append(getDigit(m));
        if (m == 0) sb.append('.');
      }
      return sb.toString();
  }

  private static int toRange(int i, int lo, int hi) {
    if (i < lo) {
      return lo;
    } else if (i > hi) {
      return hi;
    } else {
      return i;
    }
  }

  @Override
  public void populateUFieldPosition(FieldPosition fp) {
    if (fp instanceof UFieldPosition) {
      ((UFieldPosition) fp)
          .setFractionDigits((int) getPluralOperand(Operand.v), (long) getPluralOperand(Operand.f));
    }
  }
}
