/* gen.c -- Generate pseudorandom numbers. */

/*
Copyright 1999, 2000 Free Software Foundation, Inc.

This file is part of the GNU MP Library.

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

The GNU MP Library 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 Lesser General Public
License for more details.

You should have received a copy of the GNU Lesser General Public License
along with the GNU MP Library; see the file COPYING.LIB.  If not, write to
the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.
*/

/* 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" \
    "  -C a,c,m     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 lc (default), bbs\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" };
  gmp_randalg_t ralg = GMP_RAND_ALG_DEFAULT;
  char *ralg_str[] = { "lc", "bbs" };

  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:C: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 */
      case 'C':			/* User supplied LC scheme: a,c,m */
	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);
	  }
	if (c == 'c')
	  m2exp = atol (str_m);
	else
	  mpz_init_set_str (z_m, str_m, 0);

	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) * BITS_PER_MP_LIMB;
      /* Fall through.  */
#endif
    case RFUNC_mpz_urandomb:
    case RFUNC_mpz_urandomm:
      if (! lc_scheme_from_user)
	{
	  gmp_randinit (rstate, ralg, MIN (128, size));
	}
      else
	{
	  if (m2exp != 0)
	    gmp_randinit_lc_2exp (rstate, z_a, ul_adder, m2exp);
	  else
	    gmp_randinit_lc (rstate, z_a, ul_adder, z_m);
	}
      
      if (gmp_errno != GMP_ERROR_NONE)
	{
	  if (gmp_errno & GMP_ERROR_INVALID_ARGUMENT)
	    fprintf (stderr, "gen: asking for too big random state\n");
	  if (gmp_errno & GMP_ERROR_UNSUPPORTED_ARGUMENT)
	    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;
