// © 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.BigInteger;

import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_AbstractBCD;

public final class DecimalQuantity_ByteArrayBCD extends DecimalQuantity_AbstractBCD {

  /**
   * The BCD of the 16 digits of the number represented by this object. Every 4 bits of the long map
   * to one digit. For example, the number "12345" in BCD is "0x12345".
   *
   * <p>Whenever bcd changes internally, {@link #compact()} must be called, except in special cases
   * like setting the digit to zero.
   */
  private byte[] bcd = new byte[100];

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

  public DecimalQuantity_ByteArrayBCD(long input) {
    setToLong(input);
  }

  public DecimalQuantity_ByteArrayBCD(int input) {
    setToInt(input);
  }

  public DecimalQuantity_ByteArrayBCD(double input) {
    setToDouble(input);
  }

  public DecimalQuantity_ByteArrayBCD(BigInteger input) {
    setToBigInteger(input);
  }

  public DecimalQuantity_ByteArrayBCD(BigDecimal input) {
    setToBigDecimal(input);
  }

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

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

  @Override
  protected byte getDigitPos(int position) {
    if (position < 0 || position > precision) return 0;
    return bcd[position];
  }

  @Override
  protected void setDigitPos(int position, byte value) {
    assert position >= 0;
    ensureCapacity(position + 1);
    bcd[position] = value;
  }

  @Override
  protected void shiftLeft(int numDigits) {
    ensureCapacity(precision + numDigits);
    int i = precision + numDigits - 1;
    for (; i >= numDigits; i--) {
      bcd[i] = bcd[i - numDigits];
    }
    for (; i >= 0; i--) {
      bcd[i] = 0;
    }
    scale -= numDigits;
    precision += numDigits;
  }

  @Override
  protected void shiftRight(int numDigits) {
    int i = 0;
    for (; i < precision - numDigits; i++) {
      bcd[i] = bcd[i + numDigits];
    }
    for (; i < precision; i++) {
      bcd[i] = 0;
    }
    scale += numDigits;
    precision -= numDigits;
  }

  @Override
  protected void popFromLeft(int numDigits) {
    int i = precision - 1;
    for (; i >= precision - numDigits; i--) {
      bcd[i] = 0;
    }
    precision -= numDigits;
  }

  @Override
  protected void setBcdToZero() {
    for (int i = 0; i < precision; i++) {
      bcd[i] = (byte) 0;
    }
    scale = 0;
    precision = 0;
    isApproximate = false;
    origDouble = 0;
    origDelta = 0;
  }

  @Override
  protected void readIntToBcd(int n) {
    assert n != 0;
    int i = 0;
    for (; n != 0L; n /= 10L, i++) {
      bcd[i] = (byte) (n % 10);
    }
    scale = 0;
    precision = i;
  }

  private static final byte[] LONG_MIN_VALUE =
      new byte[] {8, 0, 8, 5, 7, 7, 4, 5, 8, 6, 3, 0, 2, 7, 3, 3, 2, 2, 9};

  @Override
  protected void readLongToBcd(long n) {
    assert n != 0;
    if (n == Long.MIN_VALUE) {
      // Can't consume via the normal path.
      System.arraycopy(LONG_MIN_VALUE, 0, bcd, 0, LONG_MIN_VALUE.length);
      scale = 0;
      precision = LONG_MIN_VALUE.length;
      return;
    }
    int i = 0;
    for (; n != 0L; n /= 10L, i++) {
      bcd[i] = (byte) (n % 10);
    }
    scale = 0;
    precision = i;
  }

  @Override
  protected void readBigIntegerToBcd(BigInteger n) {
    assert n.signum() != 0;
    int i = 0;
    for (; n.signum() != 0; i++) {
      BigInteger[] temp = n.divideAndRemainder(BigInteger.TEN);
      ensureCapacity(i + 1);
      bcd[i] = temp[1].byteValue();
      n = temp[0];
    }
    scale = 0;
    precision = i;
  }

  @Override
  protected BigDecimal bcdToBigDecimal() {
    // Converting to a string here is faster than doing BigInteger/BigDecimal arithmetic.
    return new BigDecimal(toDumbString());
  }

  private String toDumbString() {
    StringBuilder sb = new StringBuilder();
    if (isNegative()) sb.append('-');
    if (precision == 0) {
      sb.append('0');
      return sb.toString();
    }
    for (int i = precision - 1; i >= 0; i--) {
      sb.append(getDigitPos(i));
    }
    if (scale != 0) {
      sb.append('E');
      sb.append(scale);
    }
    return sb.toString();
  }

  @Override
  protected void compact() {
    // Special handling for 0
    boolean isZero = true;
    for (int i = 0; i < precision; i++) {
      if (bcd[i] != 0) {
        isZero = false;
        break;
      }
    }
    if (isZero) {
      scale = 0;
      precision = 0;
      return;
    }

    // Compact the number (remove trailing zeros)
    int delta = 0;
    for (; bcd[delta] == 0; delta++) ;
    shiftRight(delta);

    // Compute precision
    int leading = precision - 1;
    for (; leading >= 0 && bcd[leading] == 0; leading--) ;
    precision = leading + 1;
  }

  private void ensureCapacity(int capacity) {
    if (bcd.length >= capacity) return;
    byte[] bcd1 = new byte[capacity * 2];
    System.arraycopy(bcd, 0, bcd1, 0, bcd.length);
    bcd = bcd1;
  }

  @Override
  protected void copyBcdFrom(DecimalQuantity _other) {
    DecimalQuantity_ByteArrayBCD other = (DecimalQuantity_ByteArrayBCD) _other;
    System.arraycopy(other.bcd, 0, bcd, 0, bcd.length);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    for (int i = 30; i >= 0; i--) {
      sb.append(bcd[i]);
    }
    return String.format(
        "<DecimalQuantity3 %d:%d %s%s%d>",
        lReqPos,
        rReqPos,
        sb,
        "E",
        scale);
  }
}
