import {devAssert} from '#core/assert';

/**
 * Maps a value in a first range to its equivalent in a second range
 * Ex.: 5 in the range [0,10] gives 60 in the range[40,80]
 *
 * NOTE: lower/upper bounds on the source range are detected automatically,
 * however the bounds on the target range are not altered (thus the target
 * range could be decreasing).
 * Ex1: 8 in the range [0, 10] gives 2 in the range [10, 0]
 * Ex2: also, 8 in the range [10, 0] gives 2 in the range [10, 0]
 *
 * NOTE: Input value is enforced to be bounded inside the source range
 * Ex1: -2 in the range [0, 10] is interpreted as 0 and thus gives 40 in [40,80]
 * Ex2: 19 in the range [0, 5] is interpreted as 5 and thus gives 80 in [40,80]
 *
 * @param {number} val the value in the source range
 * @param {number} min1 the lower bound of the source range
 * @param {number} max1 the upper bound of the source range
 * @param {number} min2 the lower bound of the target range
 * @param {number} max2 the upper bound of the target range
 * @return {number} the equivalent value in the target range
 */
export function mapRange(val, min1, max1, min2, max2) {
  let max1Bound = max1;
  let min1Bound = min1;
  if (min1 > max1) {
    max1Bound = min1;
    min1Bound = max1;
  }

  if (val < min1Bound) {
    val = min1Bound;
  } else if (val > max1Bound) {
    val = max1Bound;
  }

  return ((val - min1) * (max2 - min2)) / (max1 - min1) + min2;
}

/**
 * Computes the modulus of values `a` and `b`.
 *
 * This is needed because the % operator in JavaScript doesn't implement
 * modulus behavior as can be seen by the spec here:
 * http://www.ecma-international.org/ecma-262/5.1/#sec-11.5.3.
 * It instead is used to obtain the remainder of a division.
 * This function uses the remainder (%) operator to determine the modulus.
 * Derived from here:
 * https://stackoverflow.com/questions/25726760/javascript-modular-arithmetic/47354356#47354356
 *
 * @param {number} a
 * @param {number} b
 * @return {number} returns the modulus of the two numbers.
 * @example
 *
 * _.min(10, 5);
 * // => 0
 *
 * _.mod(-1, 5);
 * // => 4
 */
export function mod(a, b) {
  return a > 0 && b > 0 ? a % b : ((a % b) + b) % b;
}

/**
 * Restricts a number to be in the given min/max range. The minimum value must
 * be less than or equal to the maximum value.
 *
 * Examples:
 * clamp(0.5, 0, 1) -> 0.5
 * clamp(1.5, 0, 1) -> 1
 * clamp(-0.5, 0, 1) -> 0
 *
 * @param {number} val the value to clamp.
 * @param {number} min the lower bound.
 * @param {number} max the upper bound.
 * @return {number} the clamped value.
 */
export function clamp(val, min, max) {
  devAssert(min <= max, 'Minimum value is greater than the maximum.');
  return Math.min(Math.max(val, min), max);
}

/**
 * Returns value bound to min and max values +/- extent. The lower bound must
 * be less than or equal to the upper bound.
 * @param {number} val the value to bound.
 * @param {number} min the lower bound.
 * @param {number} max the upper bound
 * @param {number} extent the allowed extent beyond the bounds.
 * @return {number} the bounded value.
 */
export function boundValue(val, min, max, extent) {
  devAssert(min <= max, 'Lower bound is greater than the upper bound.');
  return clamp(val, min - extent, max + extent);
}

/**
 * Returns the length of a vector given in X- and Y-coordinates.
 * @param {number} deltaX distance in the X direction.
 * @param {number} deltaY distance in the Y direction.
 * @return {number} the magnitude of the vector.
 */
export function magnitude(deltaX, deltaY) {
  return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
}

/**
 * Returns the distance between two points.
 * @param {number} x1 X-coordinate of the first point.
 * @param {number} y1 Y-coordinate of the first point.
 * @param {number} x2 X-coordinate of the second point.
 * @param {number} y2 Y-coordinate of the second point.
 * @return {number} the distance between the two points.
 */
export function distance(x1, y1, x2, y2) {
  return magnitude(x2 - x1, y2 - y1);
}

/**
 * @param {number} centerX
 * @param {number} centerY
 * @param {number} radius
 * @param {number} angleInDegrees
 * @return {{
 *  x: number,
 *  y: number,
 * }}
 */
export function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
  const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0;

  return {
    x: centerX + radius * Math.cos(angleInRadians),
    y: centerY + radius * Math.sin(angleInRadians),
  };
}

/**
 * Sums up the values of the given array and returns the result
 * @param {Array<number>} values
 * @return {number}
 */
export function sum(values) {
  return values.reduce(function (a, b) {
    return a + b;
  });
}
