import JSBD, { Decimal } from "JSBD";

const defaultSetting = {
  symbol: "￥",
  separator: ",",
  decimal: ".",
  precision: 2,
  pattern: "!#",
  negativePattern: "-!#",
  showTail0: true,
  strict: false,
  fromCents: false,
};
export class currency {
  JSBDVal: Decimal;
  value: string;
  private settings: {
    symbol: string;
    separator: string;
    decimal: string;
    precision: number;
    pattern: string;
    negativePattern: string;
    formatter?: (i: string) => string;
    showTail0: boolean;
    strict: boolean;
    fromCents: boolean;
  };

  constructor(
    value: number | string | Decimal | bigint | currency,
    opts?: {
      symbol?: string;
      separator?: string;
      decimal?: string;
      precision?: number;
      pattern?: string;
      negativePattern?: string;
      formatter?: (i: string) => string;
      showTail0?: boolean;
      strict?: boolean;
      fromCents?: boolean;
    }
  ) {
    this.settings = Object.assign({}, defaultSetting, opts);
    this.JSBDVal = this.parseInput(value);
    if (this.settings.fromCents)
      this.JSBDVal = JSBD.divide(this.JSBDVal, JSBD.BigDecimal(100));
    this.settings.fromCents = false;
    this.value = this.JSBDVal.toFixed(this.settings.precision)
      .replace(/0+$/, "")
      .replace(/\.$/, "")
      .replace(/^$/, "0");
  }

  private static toChineseMoney(num: string): string {
    let ch = ["零", "壹", "贰", "叁", "肆", "伍", "陆", "柒", "捌", "玖"],
      ch_u = ["个", "拾", "佰", "仟", "万", "亿"];
    let isNegative = num.charAt(0) === "-";
    num = isNegative ? num.substring(1) : num;
    let [int, decimal] = num.split(".");
    let int_str = decimal === "00" ? "整" : "",
      decimal_str = "";
    if (decimal === "00") decimal_str = "";
    else if (decimal.charAt(0) === "0")
      decimal_str = "零" + ch[+decimal.charAt(1)] + "分";
    else if (decimal.charAt(1) === "0")
      decimal_str = ch[+decimal.charAt(0)] + "角";
    else
      decimal_str =
        ch[+decimal.charAt(0)] + "角" + ch[+decimal.charAt(1)] + "分";
    if (int != "0" || decimal_str === "") {
      const int2ChineseNumber = (intStr: string) => {
        intStr = intStr.replace(/^0+/, "").replace(/^$/, "0");
        let int = "",
          strLen = intStr.length;

        if (strLen == 1) return ch[+intStr];
        if (strLen <= 4) {
          for (let i = 0, n = strLen - 1; n >= 0; n--, i++) {
            let _num = +intStr.charAt(i);
            int += ch[_num];
            int += _num && n ? ch_u[n] : "";
          }
        } else {
          //大数递归
          let d = (intStr.length / 4) >> 0,
            y = intStr.length % 4;
          while (y == 0 || !ch_u[3 + d]) {
            y += 4;
            d--;
          }
          let left = intStr.substring(0, y),
            right = intStr.substring(y);

          int =
            int2ChineseNumber(left) +
            ch_u[3 + d] +
            (right.charAt(0) == "0" ? "零" : "") +
            int2ChineseNumber(right);
        }
        return int.replace(/零+$/, "").replace(/零{2}/g, "零");
      };
      int_str = int2ChineseNumber(int) + "元" + int_str;
    }
    return (isNegative ? "负" : "") + int_str + decimal_str;
  }
  private parseInput(
    val: number | string | currency | Decimal | BigInt
  ): Decimal {
    if (!this.testValid(val)) throw "number format error";
    if (typeof val === "undefined" || val === null) return JSBD.BigDecimal(0);
    if (val instanceof currency) return JSBD.BigDecimal(val.JSBDVal);
    if (val instanceof Decimal) return JSBD.BigDecimal(val);
    if (typeof val === "string") {
      let regex = new RegExp("[^-\\d" + this.settings.decimal + "]", "g"),
        decimalString = new RegExp("\\" + this.settings.decimal, "g");
      val = val
        .replace(/\((.*)\)/, "-$1")
        .replace(regex, "")
        .replace(decimalString, ".")
        .replace(/^0+/, "")
        .replace(/^$/, "0");
      //('123ab.f3')=>'123.3'
      return JSBD.BigDecimal(val);
    }
    return JSBD.BigDecimal(<number | bigint>val);
  }
  private testValid(
    val: number | string | currency | Decimal | BigInt
  ): boolean {
    if (this.settings.strict === false) return true;
    if (val === null || typeof val === "undefined") return false;
    if (typeof val === "string") return /^[-]?[\.\d]+$/.test(val);
    return true;
  }

  add(val: number | string | currency | Decimal | BigInt): currency {
    return new currency(
      JSBD.add(this.JSBDVal, this.parseInput(val)),
      this.settings
    );
  }
  subtract(val: number | string | currency | Decimal | BigInt): currency {
    return new currency(
      JSBD.subtract(this.JSBDVal, this.parseInput(val)),
      this.settings
    );
  }
  multiply(val: number | string | currency | Decimal | BigInt): currency {
    return new currency(
      JSBD.multiply(this.JSBDVal, this.parseInput(val)),
      this.settings
    );
  }
  divide(val: number | string | currency | Decimal | BigInt): currency {
    return new currency(
      JSBD.divide(this.JSBDVal, this.parseInput(val)),
      this.settings
    );
  }
  compareTo(b: currency): number {
    if (JSBD.greaterThan(this.JSBDVal, b.JSBDVal)) return 1;
    if (JSBD.equal(this.JSBDVal, b.JSBDVal)) return 0;
    return -1;
  }
  distribute(count: number): Array<currency> {
    count = Math.floor(count);
    const arr: currency[] = [];
    const divideRes = JSBD.divide(this.JSBDVal, JSBD.BigDecimal(count), {
      maximumFractionDigits: this.settings.precision,
      roundingMode: "down",
    });
    const reminder = JSBD.subtract(
      this.JSBDVal,
      JSBD.multiply(divideRes, JSBD.BigDecimal(count))
    );
    let minUnit =
      this.settings.precision === 0
        ? JSBD.BigDecimal(1)
        : JSBD.pow(JSBD.BigDecimal(0.1), this.settings.precision);
    if (JSBD.greaterThan(JSBD.BigDecimal(0), reminder))
      minUnit = JSBD.multiply(minUnit, JSBD.BigDecimal(-1));
    let num = +JSBD.divide(reminder, minUnit).toFixed();
    for (let i = 0; i < count; i++, num--) {
      if (num > 0)
        arr[i] = new currency(JSBD.add(divideRes, minUnit), this.settings);
      else arr[i] = new currency(divideRes, this.settings);
    }
    return arr;
  }

  format(opts?: {
    symbol?: string;
    separator?: string;
    decimal?: string;
    precision?: string;
    pattern?: string;
    negativePattern?: string;
    showTail0?: true;
    formatter?: (arg0: string) => string;
  }): string {
    let { pattern, negativePattern, symbol, separator, decimal, formatter } =
      Object.assign({}, this.settings, opts);
    if (formatter) return formatter(this.value);
    let split = this.JSBDVal.toFixed(this.settings.precision)
        .replace(/^-/, "")
        .split("."),
      yuan = split[0],
      cents = split[1];
    if (!this.settings.showTail0) {
      cents = cents.replace(/0+$/, "");
      if (cents.length === 0) decimal = "";
    }
    return (
      JSBD.greaterThanOrEqual(this.JSBDVal, JSBD.BigDecimal(0))
        ? pattern
        : negativePattern
    )
      .replace("!", symbol)
      .replace(
        "#",
        yuan.replace(/(\d)(?=(\d{3})+\b)/g, "$1" + separator) +
          (cents ? decimal + cents : "")
      );
  }
  toString(): string {
    return this.value;
  }
  toNumber(): number {
    return parseFloat(this.value);
  }
  toJSON(): string {
    return this.value;
  }
  yuan(): string {
    return this.value;
  }
  cents(): string {
    return JSBD.multiply(this.JSBDVal, JSBD.BigDecimal(100)).toFixed();
  }
  chineseNumber(): string {
    return currency.toChineseMoney(this.JSBDVal.toFixed(2));
  }
}

export default currency;
