Source: ui/amount.js

/*!
 * amount.js - amount object for hsd
 * Copyright (c) 2017-2018, Christopher Jeffrey (MIT License).
 * https://github.com/handshake-org/hsd
 */

'use strict';

const assert = require('bsert');
const fixed = require('../utils/fixed');
const {EXP} = require('../protocol/consensus');
const pkg = require('../pkg');

/** @typedef {import('../types').AmountUnitType} AmountUnitType */
/** @typedef {import('../types').Amount} AmountValue */

/**
 * Amount
 * Represents a currency amount (base unit internally).
 * @alias module:currency.Amount
 * @property {Number} value
 */

class Amount {
  /**
   * Create an amount.
   * @constructor
   * @param {(String|Number)?} [value]
   * @param {AmountUnitType} [unit=doo]
   */

  constructor(value, unit) {
    this.value = 0;

    if (value != null)
      this.fromOptions(value, unit);
  }

  /**
   * Inject properties from options.
   * @param {String|Number} value
   * @param {AmountUnitType} [unit=doo]
   * @returns {Amount}
   */

  fromOptions(value, unit) {
    if (typeof unit === 'string')
      return this.from(unit, value);

    if (typeof value === 'number')
      return this.fromValue(value);

    return this.fromCoins(value);
  }

  /**
   * Get base unit value.
   * @returns {AmountValue}
   */

  toValue() {
    return this.value;
  }

  /**
   * Get base unit string or value.
   * @param {Boolean} [num=false] - Return a number.
   * @returns {String|AmountValue}
   */

  toBase(num) {
    if (num)
      return this.value;

    return this.value.toString(10);
  }

  /**
   * Get mhns string or value.
   * @param {Boolean} [num=false] - Return a number.
   * @returns {String|AmountValue}
   */

  toMilli(num) {
    return Amount.encode(this.value, 3, num);
  }

  /**
   * Get currency string or value.
   * @param {Boolean} [num=false] - Return a number.
   * @returns {String|AmountValue}
   */

  toCoins(num) {
    return Amount.encode(this.value, EXP, num);
  }

  /**
   * Get unit string or value.
   * @param {AmountUnitType} unit
   * @param {Boolean} [num=false] - Return a number.
   * @returns {String|AmountValue}
   * @throws on incorrect unit type.
   */

  to(unit, num) {
    switch (unit) {
      case pkg.base:
      case `u${pkg.unit}`:
        return this.toBase(num);
      case `m${pkg.unit}`:
        return this.toMilli(num);
      case pkg.unit:
      case pkg.currency:
        return this.toCoins(num);
    }
    throw new Error(`Unknown unit "${unit}".`);
  }

  /**
   * Convert amount to currency string.
   * @returns {String|AmountValue}
   */

  toString() {
    return this.toCoins();
  }

  /**
   * Inject properties from value.
   * @param {AmountValue} value
   * @returns {Amount}
   */

  fromValue(value) {
    assert(Number.isSafeInteger(value) && value >= 0,
      'Value must be an int64.');
    this.value = value;
    return this;
  }

  /**
   * Inject properties from base unit.
   * @param {Number|String} value
   * @returns {Amount}
   */

  fromBase(value) {
    this.value = Amount.decode(value, 0);
    return this;
  }

  /**
   * Inject properties from mhns.
   * @param {Number|String} value
   * @returns {Amount}
   */

  fromMilli(value) {
    this.value = Amount.decode(value, 3);
    return this;
  }

  /**
   * Inject properties from value.
   * @param {Number|String} value
   * @returns {Amount}
   */

  fromCoins(value) {
    this.value = Amount.decode(value, EXP);
    return this;
  }

  /**
   * Inject properties from unit.
   * @param {AmountUnitType} unit
   * @param {Number|String} value
   * @returns {Amount}
   * @throws on incorrect unit type.
   */

  from(unit, value) {
    switch (unit) {
      case pkg.base:
      case `u${pkg.unit}`:
        return this.fromBase(value);
      case `m${pkg.unit}`:
        return this.fromMilli(value);
      case pkg.unit:
      case pkg.currency:
        return this.fromCoins(value);
    }
    throw new Error(`Unknown unit "${unit}".`);
  }

  /**
   * Instantiate amount from options.
   * @param {String|Number} value
   * @param {AmountUnitType} [unit=doo]
   * @returns {Amount}
   */

  static fromOptions(value, unit) {
    return new this().fromOptions(value, unit);
  }

  /**
   * Instantiate amount from value.
   * @param {AmountValue} value
   * @returns {Amount}
   */

  static fromValue(value) {
    return new this().fromValue(value);
  }

  /**
   * Instantiate amount from base unit.
   * @param {Number|String} value
   * @returns {Amount}
   */

  static fromBase(value) {
    return new this().fromBase(value);
  }

  /**
   * Instantiate amount from milliunit.
   * @param {Number|String} value
   * @returns {Amount}
   */

  static fromMilli(value) {
    return new this().fromMilli(value);
  }

  /**
   * Instantiate amount from unit.
   * @param {Number|String} value
   * @returns {Amount}
   */

  static fromCoins(value) {
    return new this().fromCoins(value);
  }

  /**
   * Instantiate amount from unit.
   * @param {AmountUnitType} unit
   * @param {Number|String} value
   * @returns {Amount}
   */

  static from(unit, value) {
    return new this().from(unit, value);
  }

  /**
   * Inspect amount.
   * @returns {String}
   */

  inspect() {
    return `<Amount: ${this.toString()}>`;
  }

  /**
   * Safely convert base unit to a currency string.
   * This function explicitly avoids any
   * floating point arithmetic.
   * @param {AmountValue} value - Base unit.
   * @param {Boolean} [num=false] - Return a number.
   * @returns {String|Number} Currency string.
   */

  static coin(value, num) {
    if (typeof value === 'string')
      return value;

    return Amount.encode(value, EXP, num);
  }

  /**
   * Safely convert a currency string to base unit.
   * @param {String} str
   * @returns {AmountValue} Base unit.
   * @throws on parse error
   */

  static value(str) {
    if (typeof str === 'number')
      return str;

    return Amount.decode(str, EXP);
  }

  /**
   * Safely convert base unit to a currency string.
   * @param {AmountValue} value
   * @param {Number} exp - Exponent.
   * @param {Boolean} [num=false] - Return a number.
   * @returns {String|Number}
   */

  static encode(value, exp, num) {
    if (num)
      return fixed.toFloat(value, exp);
    return fixed.encode(value, exp);
  }

  /**
   * Safely convert a currency string to base unit.
   * @param {String|Number} value
   * @param {Number} exp - Exponent.
   * @returns {AmountValue} Base unit.
   * @throws on parse error
   */

  static decode(value, exp) {
    if (typeof value === 'number')
      return fixed.fromFloat(value, exp);
    return fixed.decode(value, exp);
  }
}

/*
 * Expose
 */

module.exports = Amount;