import { isAnyArray } from 'is-any-array';
import max from 'ml-array-max';
import min from 'ml-array-min';

export interface ArrayRescaleOptions<T extends ArrayLike<number>> {
  /**
   * @default 0
   */
  min?: number;
  /**
   * @default 1
   */
  max?: number;
  /**
   * If `true` and min or max is undefined take the min or max from input array.
   * @default false
   */
  autoMinMax?: boolean;
  /**
   * Specify the output array. Can be the input array for in place modification.
   */
  output?: T;
}

/**
 * Rescale an array into a range.
 */
export default function rescale<T extends ArrayLike<number>>(
  input: T,
  options: ArrayRescaleOptions<T> = {},
): T | number[] {
  if (!isAnyArray(input)) {
    throw new TypeError('input must be an array');
  } else if (input.length === 0) {
    throw new TypeError('input must not be empty');
  }

  let output;
  if (options.output !== undefined) {
    if (!isAnyArray(options.output)) {
      throw new TypeError('output option must be an array if specified');
    }
    output = options.output;
  } else {
    output = new Array(input.length);
  }

  const currentMin = min(input);
  const currentMax = max(input);

  if (currentMin === currentMax) {
    throw new RangeError(
      'minimum and maximum input values are equal. Cannot rescale a constant array',
    );
  }

  const {
    min: minValue = options.autoMinMax ? currentMin : 0,
    max: maxValue = options.autoMinMax ? currentMax : 1,
  } = options;

  if (minValue >= maxValue) {
    throw new RangeError('min option must be smaller than max option');
  }

  const factor = (maxValue - minValue) / (currentMax - currentMin);
  for (let i = 0; i < input.length; i++) {
    output[i] = (input[i] - currentMin) * factor + minValue;
  }

  return output;
}
