import { sprintf, __ } from '~/locale';
import {
  BYTES_IN_KIB,
  THOUSAND,
  BYTES_FORMAT_BYTES,
  BYTES_FORMAT_KIB,
  BYTES_FORMAT_MIB,
  BYTES_FORMAT_GIB,
} from './constants';

/**
 * Function that allows a number with an X amount of decimals
 * to be formatted in the following fashion:
 * * For 1 digit to the left of the decimal point and X digits to the right of it
 * * * Show 3 digits to the right
 * * For 2 digits to the left of the decimal point and X digits to the right of it
 * * * Show 2 digits to the right
 */
export function formatRelevantDigits(number) {
  let digitsLeft = '';
  let relevantDigits = 0;
  let formattedNumber = '';
  if (!Number.isNaN(Number(number))) {
    [digitsLeft] = number.toString().split('.');
    switch (digitsLeft.length) {
      case 1:
        relevantDigits = 3;
        break;
      case 2:
        relevantDigits = 2;
        break;
      case 3:
        relevantDigits = 1;
        break;
      default:
        relevantDigits = 4;
        break;
    }
    formattedNumber = Number(number).toFixed(relevantDigits);
  }
  return formattedNumber;
}

/**
 * Utility function that calculates KiB of the given bytes.
 *
 * @param  {Number} number bytes
 * @return {Number}        KiB
 */
export function bytesToKiB(number) {
  return number / BYTES_IN_KIB;
}

/**
 * Utility function that calculates MiB of the given bytes.
 *
 * @param  {Number} number bytes
 * @return {Number}        MiB
 */
export function bytesToMiB(number) {
  return number / (BYTES_IN_KIB * BYTES_IN_KIB);
}

/**
 * Utility function that calculates GiB of the given bytes.
 * @param {Number} number
 * @returns {Number}
 */
export function bytesToGiB(number) {
  return number / (BYTES_IN_KIB * BYTES_IN_KIB * BYTES_IN_KIB);
}

/**
 * Formats the bytes in number into a more understandable
 * representation. Returns an array with the first value being the human size
 * and the second value being the format (e.g., [1.5, 'KiB']).
 *
 * @param {Number} size
 * @param {Number} digits - The number of digits to appear after the decimal point
 * @returns {String}
 */
export function numberToHumanSizeSplit(size, digits = 2) {
  const abs = Math.abs(size);

  if (abs < BYTES_IN_KIB) {
    return [size.toString(), BYTES_FORMAT_BYTES];
  } else if (abs < BYTES_IN_KIB ** 2) {
    return [bytesToKiB(size).toFixed(digits), BYTES_FORMAT_KIB];
  } else if (abs < BYTES_IN_KIB ** 3) {
    return [bytesToMiB(size).toFixed(digits), BYTES_FORMAT_MIB];
  }
  return [bytesToGiB(size).toFixed(digits), BYTES_FORMAT_GIB];
}

/**
 * Port of rails number_to_human_size
 * Formats the bytes in number into a more understandable
 * representation (e.g., giving it 1500 yields 1.5 KB).
 *
 * @param {Number} size
 * @param {Number} digits - The number of digits to appear after the decimal point
 * @returns {String}
 */
export function numberToHumanSize(size, digits = 2) {
  const [humanSize, format] = numberToHumanSizeSplit(size, digits);

  switch (format) {
    case BYTES_FORMAT_BYTES:
      return sprintf(__('%{size} bytes'), { size: humanSize });
    case BYTES_FORMAT_KIB:
      return sprintf(__('%{size} KiB'), { size: humanSize });
    case BYTES_FORMAT_MIB:
      return sprintf(__('%{size} MiB'), { size: humanSize });
    case BYTES_FORMAT_GIB:
      return sprintf(__('%{size} GiB'), { size: humanSize });
    default:
      return '';
  }
}

/**
 * Converts a number to kilos or megas.
 *
 * For example:
 * - 123 becomes 123
 * - 123456 becomes 123.4k
 * - 123456789 becomes 123.4m
 *
 * @param number Number to format
 * @param digits The number of digits to appear after the decimal point
 * @return {string} Formatted number
 */
export function numberToMetricPrefix(number, digits = 1) {
  if (number < THOUSAND) {
    return number.toString();
  }
  if (number < THOUSAND ** 2) {
    return `${(number / THOUSAND).toFixed(digits)}k`;
  }
  return `${(number / THOUSAND ** 2).toFixed(digits)}m`;
}
/**
 * A simple method that returns the value of a + b
 * It seems unessesary, but when combined with a reducer it
 * adds up all the values in an array.
 *
 * e.g. `[1, 2, 3, 4, 5].reduce(sum) // => 15`
 *
 * @param {Float} a
 * @param {Float} b
 * @example
 * // return 15
 * [1, 2, 3, 4, 5].reduce(sum);
 *
 * // returns 6
 * Object.values([{a: 1, b: 2, c: 3].reduce(sum);
 * @returns {Float} The summed value
 */
export const sum = (a = 0, b = 0) => a + b;

/**
 * Checks if the provided number is odd
 * @param {Int} number
 */
export const isOdd = (number = 0) => number % 2;

/**
 * Computes the median for a given array.
 * @param {Array} arr An array of numbers
 * @returns {Number} The median of the given array
 */
export const median = (arr) => {
  const middle = Math.floor(arr.length / 2);
  const sorted = arr.sort((a, b) => a - b);
  return arr.length % 2 !== 0 ? sorted[middle] : (sorted[middle - 1] + sorted[middle]) / 2;
};

/**
 * Computes the change from one value to the other as a percentage.
 * @param {Number} firstY
 * @param {Number} lastY
 * @returns {Number}
 */
export const changeInPercent = (firstY, lastY) => {
  if (firstY === lastY) {
    return 0;
  }

  return Math.round(((lastY - firstY) / Math.abs(firstY)) * 100);
};

/**
 * Computes and formats the change from one value to the other as a percentage.
 * Prepends the computed percentage with either "+" or "-" to indicate an in- or decrease and
 * returns a given string if the result is not finite (for example, if the first value is "0").
 * @param firstY
 * @param lastY
 * @param nonFiniteResult
 * @returns {String}
 */
export const formattedChangeInPercent = (firstY, lastY, { nonFiniteResult = '-' } = {}) => {
  const change = changeInPercent(firstY, lastY);

  if (!Number.isFinite(change)) {
    return nonFiniteResult;
  }

  return `${change >= 0 ? '+' : ''}${change}%`;
};

/**
 * Checks whether a value is numerical in nature by converting it using parseInt
 *
 * Example outcomes:
 *   - isNumeric(100) = true
 *   - isNumeric('100') = true
 *   - isNumeric(1.0) = true
 *   - isNumeric('1.0') = true
 *   - isNumeric('abc100') = false
 *   - isNumeric('abc') = false
 *   - isNumeric(true) = false
 *   - isNumeric(undefined) = false
 *   - isNumeric(null) = false
 *
 * @param value
 * @returns {boolean}
 */
export const isNumeric = (value) => {
  return !Number.isNaN(parseInt(value, 10));
};

const numberRegex = /^[0-9]+$/;

/**
 * Checks whether the value is a positive number or 0, or a string with equivalent value
 *
 * @param value
 * @return {boolean}
 */
export const isPositiveInteger = (value) => numberRegex.test(value);
