import { utils as EthersUtils, BigNumber } from 'ethers'

export function bigNum(value) {
  return BigNumber.from(value)
}

export function max(a, b) {
  return a.gt(b) ? a : b
}

export function min(a, b) {
  return a.lt(b) ? a : b
}

export function formatTokenAmount(
  amount,
  isIncoming,
  decimals = 0,
  displaySign = false
) {
  return (
    (displaySign ? (isIncoming ? '+' : '-') : '') +
    formatUnits(amount, { digits: decimals })
  )
}

/**
 * Format a decimal-based number back to a normal number
 *
 * @param {string} value the number
 * @param {number} digits number of decimal places
 * @returns {BN} value converted to it's normal representation
 */
export function parseUnits(value, digits) {
  return EthersUtils.parseUnits(value, digits)
}

/**
 * Format an amount of units to be displayed.
 *
 * @param {BigNumber} value Amount of units to format.
 * @param {Number} options.digits Amount of digits on the token.
 * @param {Boolean} options.commas Use comma-separated groups.
 * @param {Boolean} options.replaceZeroBy The string to be returned when value is zero.
 * @param {Boolean} options.precision The precision of the resulting number
 * @returns {String} value formatted
 */
export function formatUnits(
  value,
  { digits = 18, commas = true, replaceZeroBy = '0', precision = 2 } = {}
) {
  if (value.lt(0) || digits < 0) {
    return ''
  }

  let valueBeforeCommas = EthersUtils.formatUnits(value.toString(), digits)

  // Replace 0 by an empty value
  if (valueBeforeCommas === '0.0') {
    return replaceZeroBy
  }

  // EthersUtils.formatUnits() adds a decimal even when 0, this removes it.
  valueBeforeCommas = valueBeforeCommas.replace(/\.0$/, '')

  const roundedValue = round(valueBeforeCommas, precision)

  return commas ? EthersUtils.commify(roundedValue) : roundedValue
}

/**
 * Format an amount of units to be displayed.
 *
 * @param {String} value Value to round
 * @param {Number} precision Rounding precision
 * @returns {String} Value rounded to `precision` decimals
 */
export function round(value, precision = 2) {
  let [whole, decimal] = value.split('.')

  if (!decimal || decimal.length <= precision) return value

  // Round and keep the last `precision` digits
  decimal = Math.round(parseInt((decimal || '0').slice(0, precision + 2)) / 100)

  return `${whole}${decimal ? `.${decimal}` : ''}`
}

const wordNumbers = [
  'one',
  'two',
  'three',
  'four',
  'five',
  'six',
  'seven',
  'eight',
  'nine',
]

export function numberToWord(number) {
  return wordNumbers[number]
}

const ordinalNumbers = [
  'first',
  'second',
  'third',
  'fourth',
  'fifth',
  'sixth',
  'seventh',
  'eighth',
  'ninth',
]

export function numberToOrdinal(number) {
  return ordinalNumbers[number - 1]
}

export function getPercentage(value, totalValue) {
  if (!totalValue > 0) return 0

  return Math.round((value * 100) / totalValue, 2)
}

export function getPercentageBN(value, totalValue) {
  const valueAsNumber = Number(EthersUtils.formatUnits(value, 18))
  const totalValueAsNumber = Number(EthersUtils.formatUnits(totalValue, 18))

  const PERCENT_BN = 100

  if (totalValue.lte(0)) return 0

  return (valueAsNumber * PERCENT_BN) / totalValueAsNumber
}

export function generateRandomNumber() {
  const code = BigNumber.from(EthersUtils.randomBytes(32))
  return code.toHexString().slice(2)
}

/**
 * Normalizes a number from another range into a value between 0 and 1.
 *
 * Identical to map(value, low, high, 0, 1)
 * Numbers outside the range are not clamped to 0 and 1, because out-of-range
 * values are often intentional and useful.
 *
 * From Processing.js
 *
 * @param {Number} aNumber The incoming value to be converted
 * @param {Number} low Lower bound of the value's current range
 * @param {Number} high Upper bound of the value's current range
 * @returns {Number} Normalized number
 */
export function norm(aNumber, low, high) {
  return (aNumber - low) / (high - low)
}

/**
 * Calculates a number between two numbers at a specific increment. The
 * progress parameter is the amount to interpolate between the two values where
 * 0.0 equal to the first point, 0.1 is very near the first point, 0.5 is
 * half-way in between, etc. The lerp function is convenient for creating
 * motion along a straight path and for drawing dotted lines.
 *
 * From Processing.js
 *
 * @param {Number} progress Between 0.0 and 1.0
 * @param {Number} value1 First value
 * @param {Number} value2 Second value
 * @returns {Number} Increment value
 */
export function lerp(progress, value1, value2) {
  return (value2 - value1) * progress + value1
}

export function toFixed(x) {
  let e
  if (Math.abs(x) < 1.0) {
    e = parseInt(x.toString().split('e-')[1])
    if (e) {
      x *= Math.pow(10, e - 1)
      x = '0.' + new Array(e).join('0') + x.toString().substring(2)
    }
  } else {
    e = parseInt(x.toString().split('+')[1])
    if (e > 20) {
      e -= 20
      x /= Math.pow(10, e)
      x += new Array(e + 1).join('0')
    }
  }
  return x
}

export function truncateDecimals(x) {
  const fixedResult = toFixed(x)
  const result = fixedResult
    .toString()
    .match(/([0-9]+)?\.(0+)?[1-9]{1}[0-9]{1}[0-9]{1}/g)

  if (result === null) return 0

  return result[0]
}