import {
  Duration,
  enforce,
  enforce_nonnull,
} from "vscript";

// FIXME: switch RNG, need to be able to explicitly set a deterministic seed

export function random_number(min: number, max: number, pars?: { prefer_boundaries?: boolean }) {
  enforce(min <= max);
  if ((pars?.prefer_boundaries ?? true) && Math.random() < 0.25) {
    if (min < 0 && max > 0 && Math.random() < 0.33) {
      return 0;
    }
    return Math.random() < 0.5 ? min : max;
  }
  return min + (max - min) * Math.random();
}

export function maybe_null<T>(x: T): T | null {
  if (Math.random() < 0.2) {
    return null;
  }
  return x;
}

export function random_int(min: number, max: number, pars?: { prefer_boundaries?: boolean }) {
  enforce(Math.round(min) === min && Math.round(max) === max);
  return Math.round(random_number(min, max, pars));
}

export function random_choice<T>(choices: readonly T[], pars?: { prefer_boundaries?: boolean }): T {
  if (choices.length === 0) {
    throw new Error("random_choice requires a nonempty list of choices");
  }
  return choices[random_int(0, choices.length - 1, pars)]!;
}

/* counts code points (I guess?), as opposed to our byte-counting software -- doesn't matter though, since our validators should protect us against invalid input */
export function random_string(
  minLength: number,
  maxLength: number,
  pars?: { permitNullBytes?: boolean },
) {
  // slow, may want to optimize this at some point
  let result = "";
  const L = enforce_nonnull(random_int(minLength, maxLength));
  for (let i = 0; i < L; ++i) {
    const n = random_int(pars?.permitNullBytes ?? true ? 1 : 0, 65535);
    if (n > 255) {
      result += String.fromCharCode(n >> 8, n & 0xff);
    } else {
      result += String.fromCharCode(n);
    }
  }
  return result;
}

export function log_rand(min: number, max: number) {
  enforce(min > 0 && max > 0);
  const ordersOfMagnitude = Math.log(max) - Math.log(min);
  return Math.min(max, Math.max(min, Math.exp(Math.log(min) + Math.random() * ordersOfMagnitude)));
}

// NOTE: using this strange A extends T[] construction to conveniently deal with
// AT1101.Something[] | AT1130.Something[]
export const random_subset = <T, A extends T[]>(choices: A, num_choices: number): A => {
  const _choices = [...choices];
  const selections = [] as unknown as A;
  for (let i = 0; i < num_choices; ++i) {
    const pos = random_int(0, _choices.length - 1);
    const selection = _choices.splice(pos, 1);
    selections.push(selection[0]);
    if (_choices.length <= 0) break;
  }
  return selections as A;
};

export function random_duration(
  min: Duration,
  max: Duration,
  pars?: { prefer_boundaries?: boolean },
) {
  return min.plus(max.minus(min).times(random_number(0, 1, pars)));
}
