/* gen.c -- Generate pseudorandom numbers.

Copyright 1999, 2000, 2002 Free Software Foundation, Inc.

This file is part of the GNU MP Library test suite.

The GNU MP Library test suite 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 of the License,
or (at your option) any later version.

The GNU MP Library test suite 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 GNU MP Library test suite.  If not, see http://www.gnu.org/licenses/.  */

/* Examples:

  $ gen 10
10 integers 0 <= X < 2^32 generated by mpz_urandomb()

  $ gen -f mpf_urandomb 10
10 real numbers 0 <= X < 1

  $ gen -z 127 10
10 integers 0 <= X < 2^127

  $ gen -f mpf_urandomb -x .9,1 10
10 real numbers 0 <= X < .9

  $ gen -s 1 10
10 integers, sequence seeded with 1

*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
#include <time.h>
#include <string.h>

#if !HAVE_DECL_OPTARG
extern char *optarg;
extern int optind, opterr;
#endif

#include "gmp.h"
#include "gmp-impl.h"

int main (argc, argv)
     int argc;
     char *argv[];
{
  const char usage[] =
    "usage: gen [-bhpq] [-a n] [-c a,c,m2exp] [-C a,c,m] [-f func] [-g alg] [-m n] [-s n] " \
    "[-x f,t] [-z n] [n]\n" \
    "  n        number of random numbers to generate\n" \
    "  -a n     ASCII output in radix n (default, with n=10)\n" \
    "  -b       binary output\n" \
    "  -c a,c,m2exp use supplied LC scheme\n" \
    "  -f func  random function, one of\n" \
    "           mpz_urandomb (default), mpz_urandomm, mpf_urandomb, rand, random\n" \
    "  -g alg   algorithm, one of mt (default), lc\n" \
    "  -h       print this text and exit\n" \
    "  -m n     maximum size of generated number plus 1 (0<= X < n) for mpz_urandomm\n" \
    "  -p       print used seed on stderr\n" \
    "  -q       quiet, no output\n" \
    "  -s n     initial seed (default: output from time(3))\n" \
    "  -x f,t   exclude all numbers f <= x <= t\n" \
    "  -z n     size in bits of generated numbers (0<= X <2^n) (default 32)\n" \
    "";

  unsigned long int f;
  unsigned long int n = 0;
  unsigned long int seed;
  unsigned long int m2exp = 0;
  unsigned int size = 32;
  int seed_from_user = 0;
  int ascout = 1, binout = 0, printseed = 0;
  int output_radix = 10;
  int lc_scheme_from_user = 0;
  int quiet_flag = 0;
  mpz_t z_seed;
  mpz_t z1;
  mpf_t f1;
  gmp_randstate_t rstate;
  int c, i;
  double drand;
  long lrand;
  int do_exclude = 0;
  mpf_t f_xf, f_xt;		/* numbers to exclude from sequence */
  char *str_xf, *str_xt;	/* numbers to exclude from sequence */
  char *str_a, *str_adder, *str_m;
  mpz_t z_a, z_m, z_mmax;
  unsigned long int ul_adder;

  enum
  {
    RFUNC_mpz_urandomb = 0,
    RFUNC_mpz_urandomm,
    RFUNC_mpf_urandomb,
    RFUNC_rand,
    RFUNC_random,
  } rfunc = RFUNC_mpz_urandomb;
  char *rfunc_str[] =  { "mpz_urandomb", "mpz_urandomm", "mpf_urandomb",
			 "rand", "random" };
  enum
  {
    RNG_MT = 0,
    RNG_LC
  };
  gmp_randalg_t ralg = RNG_MT;
  /* Texts for the algorithms.  The index of each must match the
     corresponding algorithm in the enum above.  */
  char *ralg_str[] = { "mt", "lc" };

  mpf_init (f_xf);
  mpf_init (f_xt);
  mpf_init (f1);
  mpz_init (z1);
  mpz_init (z_seed);
  mpz_init_set_ui (z_mmax, 0);


  while ((c = getopt (argc, argv, "a:bc:f:g:hm:n:pqs:z:x:")) != -1)
    switch (c)
      {
      case 'a':
	ascout = 1;
	binout = 0;
	output_radix = atoi (optarg);
	break;

      case 'b':
	ascout = 0;
	binout = 1;
	break;

      case 'c':			/* User supplied LC scheme: a,c,m2exp */
	if (NULL == (str_a = strtok (optarg, ","))
	    || NULL == (str_adder = strtok (NULL, ","))
	    || NULL == (str_m = strtok (NULL, ",")))
	  {
	    fprintf (stderr, "gen: bad LC scheme parameters: %s\n", optarg);
	    exit (1);
	  }
#ifdef HAVE_STRTOUL
	ul_adder = strtoul (str_adder, NULL, 0);
#elif HAVE_STRTOL
	ul_adder = (unsigned long int) strtol (str_adder, NULL, 0);
#else
	ul_adder = (unsigned long int) atoi (str_adder);
#endif

	if (mpz_init_set_str (z_a, str_a, 0))
	  {
	    fprintf (stderr, "gen: bad LC scheme parameter `a': %s\n", str_a);
	    exit (1);
	  }
	if (ULONG_MAX == ul_adder)
	  {
	    fprintf (stderr, "gen: bad LC scheme parameter `c': %s\n",
		     str_adder);
	    exit (1);
	  }
	m2exp = atol (str_m);

	lc_scheme_from_user = 1;
	break;


      case 'f':
	rfunc = -1;
	for (f = 0; f < sizeof (rfunc_str) / sizeof (*rfunc_str); f++)
	    if (!strcmp (optarg, rfunc_str[f]))
	      {
		rfunc = f;
		break;
	      }
	if (rfunc == -1)
	  {
	    fputs (usage, stderr);
	    exit (1);
	  }
	break;

      case 'g':			/* algorithm */
	ralg = -1;
	for (f = 0; f < sizeof (ralg_str) / sizeof (*ralg_str); f++)
	    if (!strcmp (optarg, ralg_str[f]))
	      {
		ralg = f;
		break;
	      }
	if (ralg == -1)
	  {
	    fputs (usage, stderr);
	    exit (1);
	  }
	break;

      case 'm':			/* max for mpz_urandomm() */
	if (mpz_set_str (z_mmax, optarg, 0))
	  {
	    fprintf (stderr, "gen: bad max value: %s\n", optarg);
	    exit (1);
	  }
	break;

      case 'p':			/* print seed on stderr */
	printseed = 1;
	break;

      case 'q':			/* quiet */
	quiet_flag = 1;
	break;

      case 's':			/* user provided seed */
	if (mpz_set_str (z_seed, optarg, 0))
	  {
	    fprintf (stderr, "gen: bad seed argument %s\n", optarg);
	    exit (1);
	  }
	seed_from_user = 1;
	break;

      case 'z':
	size = atoi (optarg);
	if (size < 1)
	  {
	    fprintf (stderr, "gen: bad size argument (-z %u)\n", size);
	    exit (1);
	  }
	break;

      case 'x':			/* Exclude. from,to */
	str_xf = optarg;
	str_xt = strchr (optarg, ',');
	if (NULL == str_xt)
	  {
	    fprintf (stderr, "gen: bad exclusion parameters: %s\n", optarg);
	    exit (1);
	  }
	*str_xt++ = '\0';
	do_exclude = 1;
	break;

      case 'h':
      case '?':
      default:
	fputs (usage, stderr);
	exit (1);
      }
  argc -= optind;
  argv += optind;

  if (! seed_from_user)
    mpz_set_ui (z_seed, (unsigned long int) time (NULL));
  seed = mpz_get_ui (z_seed);
  if (printseed)
    {
      fprintf (stderr, "gen: seed used: ");
      mpz_out_str (stderr, output_radix, z_seed);
      fprintf (stderr, "\n");
    }

  mpf_set_prec (f1, size);

  /* init random state and plant seed */
  switch (rfunc)
    {
    case RFUNC_mpf_urandomb:
#if 0
      /* Don't init a too small generator.  */
      size = PREC (f1) * GMP_LIMB_BITS;
      /* Fall through.  */
#endif
    case RFUNC_mpz_urandomb:
    case RFUNC_mpz_urandomm:
      switch (ralg)
	{
	case RNG_MT:
	  gmp_randinit_mt (rstate);
	  break;

	case RNG_LC:
	  if (! lc_scheme_from_user)
	    gmp_randinit_lc_2exp_size (rstate, MIN (128, size));
	  else
	    gmp_randinit_lc_2exp (rstate, z_a, ul_adder, m2exp);
	  break;

	default:
	  fprintf (stderr, "gen: unsupported algorithm\n");
	  exit (1);
	}

      gmp_randseed (rstate, z_seed);
      break;

    case RFUNC_rand:
      srand (seed);
      break;

    case RFUNC_random:
#ifdef __FreeBSD__		/* FIXME */
      if (seed_from_user)
	srandom (seed);
      else
	srandomdev ();
#else
      fprintf (stderr, "gen: unsupported algorithm\n");
#endif
      break;

    default:
      fprintf (stderr, "gen: random function not implemented\n");
      exit (1);
    }

  /* set up excludes */
  if (do_exclude)
    switch (rfunc)
      {
      case RFUNC_mpf_urandomb:

	if (mpf_set_str (f_xf, str_xf, 10) ||
	    mpf_set_str (f_xt, str_xt, 10))
	  {
	    fprintf (stderr, "gen: bad exclusion-from (\"%s\") " \
		     "or exclusion-to (\"%s\") string.  no exclusion done.\n",
		     str_xf, str_xt);
	    do_exclude = 0;
	  }
	break;

      default:
	fprintf (stderr, "gen: exclusion not implemented for chosen " \
		 "randomization function.  all numbers included in sequence.\n");
      }

  /* generate and print */
  if (argc > 0)
    {
#if HAVE_STRTOUL
      n = strtoul (argv[0], (char **) NULL, 10);
#elif HAVE_STRTOL
      n = (unsigned long int) strtol (argv[0], (char **) NULL, 10);
#else
      n = (unsigned long int) atoi (argv[0]);
#endif
    }

  for (f = 0; n == 0 || f < n; f++)
    {
      switch (rfunc)
	{
	case RFUNC_mpz_urandomb:
	  mpz_urandomb (z1, rstate, size);
	  if (quiet_flag)
	    break;
	  if (binout)
	    {
	      /*fwrite ((unsigned int *) z1->_mp_d, 4, 1, stdout);*/
	      fprintf (stderr, "gen: binary output for mpz_urandom* is broken\n");
	      exit (1);
	    }
	  else
	    {
	      mpz_out_str (stdout, output_radix, z1);
	      puts ("");
	    }
	  break;

	case RFUNC_mpz_urandomm:
	  mpz_urandomm (z1, rstate, z_mmax);
	  if (quiet_flag)
	    break;
	  if (binout)
	    {
	      /*fwrite ((unsigned int *) z1->_mp_d, 4, 1, stdout);*/
	      fprintf (stderr, "gen: binary output for mpz_urandom* is broken\n");
	      exit (1);
	    }
	  else
	    {
	      mpz_out_str (stdout, output_radix, z1);
	      puts ("");
	    }
	  break;

	case RFUNC_mpf_urandomb:
	  mpf_urandomb (f1, rstate, size);
	  if (do_exclude)
	    if (mpf_cmp (f1, f_xf) >= 0 && mpf_cmp (f1, f_xt) <= 0)
		break;
	  if (quiet_flag)
	    break;
	  if (binout)
	    {
	      fprintf (stderr, "gen: binary output for floating point numbers "\
		       "not implemented\n");
	      exit (1);
	    }
	  else
	    {
	      mpf_out_str (stdout, output_radix, 0, f1);
	      puts ("");
	    }
	  break;

	case RFUNC_rand:
	  i = rand ();
#ifdef FLOAT_OUTPUT
	  if (i)
	    drand = (double) i / (double) RAND_MAX;
	  else
	    drand = 0.0;
	  if (quiet_flag)
	    break;
	  if (binout)
	    fwrite (&drand, sizeof (drand), 1, stdout);
	  else
	    printf ("%e\n", drand);
#else
	  if (quiet_flag)
	    break;
	  if (binout)
	    fwrite (&i, sizeof (i), 1, stdout);
	  else
	    printf ("%d\n", i);
#endif
	  break;

	case RFUNC_random:
	  lrand = random ();
	  if (lrand)
	    drand = (double) lrand / (double) 0x7fffffff;
	  else
	    drand = 0;
	  if (quiet_flag)
	    break;
	  if (binout)
	    fwrite (&drand, sizeof (drand), 1, stdout);
	  else
	    printf ("%e\n", drand);
	  break;

	default:
	  fprintf (stderr, "gen: random function not implemented\n");
	  exit (1);
	}

    }

  /* clean up */
  switch (rfunc)
    {
    case RFUNC_mpz_urandomb:
    case RFUNC_mpf_urandomb:
      gmp_randclear (rstate);
      break;
    default:
      break;
    }
  mpf_clear (f1);
  mpf_clear (f_xf);
  mpf_clear (f_xt);
  mpz_clear (z1);
  mpz_clear (z_seed);

  return 0;
}

static void *debug_dummyz = mpz_dump;
static void *debug_dummyf = mpf_dump;
