/* rand_funcs.c:  General purpose random number functions.

   Copyright (C) 2021-2026 by Brian Lindholm.  This file is part of the
   littleutils utility set.

   The rand_funcs.c file is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3, or (at your option) any
   later version.

   The randomize utility is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
   more details.

   You should have received a copy of the GNU General Public License along with
   the littleutils.  If not, see <https://www.gnu.org/licenses/>. */

#include <config.h>

#include <fcntl.h>
#ifdef HAVE_STDIO_H
# include <stdio.h>
#endif
#ifdef HAVE_STDLIB_H
# include <stdlib.h>
#endif
#ifdef HAVE_SYS_RANDOM_H
# include <sys/random.h>
#endif
#include <time.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

#ifdef DJGPP
/* speed up stat command for DJGPP */
unsigned short _djstat_flags = 63;
#endif

#include "rand_funcs.h"

/* initialize random number generator seed */
void rand_seed()
{
  int urandom_worked = 1;
  ssize_t bytes;
#ifdef HAVE_CLOCK_GETTIME
  struct timespec junk, res;
#else
  time_t junk;
#endif
  unsigned short seed[3];

  /* pre-populate array for seed using /dev/urandom */
#ifdef HAVE_GETRANDOM
  bytes = getrandom(seed, sizeof(seed), GRND_NONBLOCK);
  if (bytes < 6) urandom_worked = 0;
#else
  int URANDOM = open("/dev/urandom", O_RDONLY);
  if (URANDOM < 0)
    urandom_worked = 0;
  else {
    /* use read instead of buffered fread to avoid grabbing extra bytes */
    bytes = read(URANDOM, seed, sizeof(seed));
    if (bytes < 6) urandom_worked = 0;
  }
  close(URANDOM);
#endif

  /* fall back to time and PID combo if /dev/urandom failed */
  if (urandom_worked == 0) {
#ifdef HAVE_CLOCK_GETTIME
    (void) clock_getres(CLOCK_MONOTONIC, &res);
    (void) clock_gettime(CLOCK_MONOTONIC, &junk);
    seed[0] = (unsigned short) ((junk.tv_nsec / res.tv_nsec) & 0xFFFF);
    seed[1] = (unsigned short) (getpid() & 0xFFFF);
    seed[2] = (unsigned short) (junk.tv_sec & 0xFFFF);
#else
    (void) time(&junk);
    seed[0] = (unsigned short) (junk & 0xFFFF);
    seed[1] = (unsigned short) (getpid() & 0xFFFF);
    seed[2] = (unsigned short) ((junk >> 16) & 0xFFFF);
#endif
  }

  /* initialize random number generator seed */
#ifdef HAVE_LRAND48
  (void) seed48(seed);
#elif defined(HAVE_RANDOM)
  srandom(((unsigned int) seed[0] << 16) + (unsigned int) seed[1]);
#else
  srand(((unsigned int) seed[0] << 16) + (unsigned int) seed[1]);
#endif
}

/* generate a random integer between 0 and n-1 */
size_t rand_int(size_t n)
{
  size_t x;

#if defined(HAVE_LRAND48)
  x = (size_t) ((double) n * drand48());
#elif defined(HAVE_RANDOM)
  x = (size_t) ((double) n * ((double) random() / (double) 2147483648.0));
#else
  x = (size_t) ((double) n * ((double) rand() / ((double) RAND_MAX + 1.0)));
#endif
  if (x > (n - 1)) x = 0;
  return(x);
}
